diff --git a/.travis.yml b/.travis.yml index 3e32645..c8fc344 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,7 +17,7 @@ before_install: - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get install -y gcc libegl1-mesa-dev libgles2-mesa-dev ; fi script: - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then export LIBRARY_PATH="$LIBRARY_PATH:/usr/local/lib" ; fi - - cargo build --verbose + - travis_wait 20 cargo build --verbose - cargo test --verbose os: - linux diff --git a/blocks/src/lib.rs b/blocks/src/lib.rs index d43387f..53c0dd8 100644 --- a/blocks/src/lib.rs +++ b/blocks/src/lib.rs @@ -1,5 +1,5 @@ -#![recursion_limit="300"] +#![recursion_limit="600"] extern crate steven_shared as shared; @@ -41,6 +41,11 @@ macro_rules! create_ids { ); } +struct VanillaIDMap { + flat: Vec>, + hier: Vec>, +} + macro_rules! define_blocks { ( $( @@ -51,6 +56,7 @@ macro_rules! define_blocks { )* }, $(data $datafunc:expr,)* + $(offset $offsetfunc:expr,)* $(material $mat:expr,)* model $model:expr, $(variant $variant:expr,)* @@ -77,24 +83,62 @@ macro_rules! define_blocks { impl Block { #[allow(unused_variables, unreachable_code)] - pub fn get_vanilla_id(&self) -> Option { + pub fn get_internal_id(&self) -> usize { + match *self { + $( + Block::$name { + $($fname,)* + } => { + internal_ids::$name + } + )+ + } + } + + #[allow(unused_variables, unreachable_code)] + pub fn get_hierarchical_data(&self) -> Option { match *self { $( Block::$name { $($fname,)* } => { $( - let data: Option = ($datafunc).map(|v| v + (internal_ids::$name << 4)); + let data: Option = ($datafunc).map(|v| v); return data; )* - Some(internal_ids::$name << 4) + Some(0) } )+ } } - pub fn by_vanilla_id(id: usize) -> Block { - VANILLA_ID_MAP.get(id).and_then(|v| *v).unwrap_or(Block::Missing{}) + #[allow(unused_variables, unreachable_code)] + pub fn get_flat_offset(&self) -> Option { + match *self { + $( + Block::$name { + $($fname,)* + } => { + $( + let offset: Option = ($offsetfunc).map(|v| v); + return offset; + )* + $( + let data: Option = ($datafunc).map(|v| v); + return data; + )* + Some(0) + } + )+ + } + } + + pub fn by_vanilla_id(id: usize, protocol_version: i32) -> Block { + if protocol_version >= 404 { + VANILLA_ID_MAP.flat.get(id).and_then(|v| *v).unwrap_or(Block::Missing{}) + } else { + VANILLA_ID_MAP.hier.get(id).and_then(|v| *v).unwrap_or(Block::Missing{}) + } } #[allow(unused_variables, unreachable_code)] @@ -210,8 +254,12 @@ macro_rules! define_blocks { } lazy_static! { - static ref VANILLA_ID_MAP: Vec> = { - let mut blocks = vec![]; + static ref VANILLA_ID_MAP: VanillaIDMap = { + let mut blocks_flat = vec![]; + let mut blocks_hier = vec![]; + let mut flat_id = 0; + let mut last_internal_id = 0; + let mut hier_block_id = 0; $({ #[allow(non_camel_case_types, dead_code)] struct CombinationIter<$($fname),*> { @@ -301,27 +349,74 @@ macro_rules! define_blocks { vals.into_iter() }),* ); + let mut last_offset: isize = -1; for block in iter { - if let Some(id) = block.get_vanilla_id() { - if blocks.len() <= id { - blocks.resize(id + 1, None); + let internal_id = block.get_internal_id(); + let hier_data: Option = block.get_hierarchical_data(); + let vanilla_id = + if let Some(hier_data) = hier_data { + if internal_id != last_internal_id { + hier_block_id += 1; + } + last_internal_id = internal_id; + Some((hier_block_id << 4) + hier_data) + } else { + None + }; + + let offset = block.get_flat_offset(); + if let Some(offset) = offset { + let id = flat_id + offset; + /* + if let Some(vanilla_id) = vanilla_id { + println!("{} block state = {:?} hierarchical {}:{} offset={}", id, block, vanilla_id >> 4, vanilla_id & 0xF, offset); + } else { + println!("{} block state = {:?} hierarchical none, offset={}", id, block, offset); } - if blocks[id].is_none() { - blocks[id] = Some(block); + */ + if offset as isize > last_offset { + last_offset = offset as isize; + } + + if blocks_flat.len() <= id { + blocks_flat.resize(id + 1, None); + } + if blocks_flat[id].is_none() { + blocks_flat[id] = Some(block); } else { panic!( - "Tried to register {:#?} to {}:{} but {:#?} was already registered", + "Tried to register {:#?} to {} but {:#?} was already registered", block, - id >> 4, - id & 0xF, - blocks[id] + id, + blocks_flat[id] ); } + + if let Some(vanilla_id) = vanilla_id { + if blocks_hier.len() <= vanilla_id { + blocks_hier.resize(vanilla_id + 1, None); + } + if blocks_hier[vanilla_id].is_none() { + blocks_hier[vanilla_id] = Some(block); + } else { + panic!( + "Tried to register {:#?} to {} but {:#?} was already registered", + block, + id, + blocks_hier[vanilla_id] + ); + } + } } } + + #[allow(unused_assignments)] + { + flat_id += (last_offset + 1) as usize; + } })+ - blocks + VanillaIDMap { flat: blocks_flat, hier: blocks_hier } }; } ); @@ -365,6 +460,7 @@ define_blocks! { snowy: bool = [false, true], }, data { if snowy { None } else { Some(0) } }, + offset { if snowy { Some(0) } else { Some(1) } }, model { ("minecraft", "grass") }, variant format!("snowy={}", snowy), tint TintType::Grass, @@ -380,6 +476,17 @@ define_blocks! { ], }, data if !snowy { Some(variant.data()) } else { None }, + offset { + if variant == DirtVariant::Podzol { + Some(variant.data() + if snowy { 0 } else { 1 }) + } else { + if snowy { + None + } else { + Some(variant.data()) + } + } + }, model { ("minecraft", variant.as_string()) }, variant { if variant == DirtVariant::Podzol { @@ -425,6 +532,7 @@ define_blocks! { stage: u8 = [0, 1], }, data Some(variant.plank_data() | ((stage as usize) << 3)), + offset Some((variant.plank_data() << 1) | (stage as usize)), material material::NON_SOLID, model { ("minecraft", format!("{}_sapling", variant.as_string()) ) }, variant format!("stage={}", stage), @@ -439,6 +547,7 @@ define_blocks! { level: u8 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], }, data Some(level as usize), + offset None, material Material { absorbed_light: 2, ..material::TRANSPARENT @@ -463,6 +572,7 @@ define_blocks! { level: u8 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], }, data Some(level as usize), + offset None, material Material { absorbed_light: 15, emitted_light: 15, @@ -513,28 +623,85 @@ define_blocks! { TreeVariant::Oak, TreeVariant::Spruce, TreeVariant::Birch, - TreeVariant::Jungle + TreeVariant::Jungle, + TreeVariant::Acacia, + TreeVariant::DarkOak, + TreeVariant::StrippedSpruce, + TreeVariant::StrippedBirch, + TreeVariant::StrippedJungle, + TreeVariant::StrippedAcacia, + TreeVariant::StrippedDarkOak, + TreeVariant::StrippedOak ], axis: Axis = [Axis::Y, Axis::Z, Axis::X, Axis::None], }, - data Some(variant.data() | (axis.index() << 2)), + data match variant { + TreeVariant::Oak | TreeVariant::Spruce | TreeVariant::Birch | TreeVariant::Jungle => + Some(variant.data() | (axis.index() << 2)), + _ => None, + }, + offset match axis { + Axis::None => None, + Axis::X => Some(variant.offset() * 3 + 0), + Axis::Y => Some(variant.offset() * 3 + 1), + Axis::Z => Some(variant.offset() * 3 + 2), + }, model { ("minecraft", format!("{}_log", variant.as_string()) ) }, variant format!("axis={}", axis.as_string()), } + Wood { + props { + variant: TreeVariant = [ + TreeVariant::Oak, + TreeVariant::Spruce, + TreeVariant::Birch, + TreeVariant::Jungle, + TreeVariant::Acacia, + TreeVariant::DarkOak, + TreeVariant::StrippedSpruce, + TreeVariant::StrippedBirch, + TreeVariant::StrippedJungle, + TreeVariant::StrippedAcacia, + TreeVariant::StrippedDarkOak, + TreeVariant::StrippedOak + ], + axis: Axis = [Axis::X, Axis::Y, Axis::Z], + }, + data None::, + offset Some(variant.offset() * 3 + axis.index()), + model { ("minecraft", format!("{}_wood", variant.as_string()) ) }, + variant format!("axis={}", axis.as_string()), + } Leaves { props { variant: TreeVariant = [ TreeVariant::Oak, TreeVariant::Spruce, TreeVariant::Birch, - TreeVariant::Jungle + TreeVariant::Jungle, + TreeVariant::Acacia, + TreeVariant::DarkOak ], decayable: bool = [false, true], check_decay: bool = [false, true], + distance: u8 = [1, 2, 3, 4, 5, 6, 7], + }, + data match variant { + TreeVariant::Oak | TreeVariant::Spruce | TreeVariant::Birch | TreeVariant::Jungle => + if distance == 1 { + Some(variant.data() + | (if decayable { 0x4 } else { 0x0 }) + | (if check_decay { 0x8 } else { 0x0 })) + } else { + None + }, + _ => None, + }, + offset if check_decay { + None + } else { + Some(variant.offset() * (7 * 2) + ((distance as usize - 1) << 1) | (if decayable { 0 } else { 1 })) }, - data Some(variant.data() - | (if decayable { 0x4 } else { 0x0 }) - | (if check_decay { 0x8 } else { 0x0 })), material material::LEAVES, model { ("minecraft", format!("{}_leaves", variant.as_string()) ) }, tint TintType::Foliage, @@ -573,6 +740,7 @@ define_blocks! { triggered: bool = [false, true], }, data Some(facing.index() | (if triggered { 0x8 } else { 0x0 })), + offset Some((facing.offset() << 1) | (if triggered { 0 } else { 1 })), model { ("minecraft", "dispenser") }, variant format!("facing={}", facing.as_string()), } @@ -588,11 +756,46 @@ define_blocks! { model { ("minecraft", variant.as_string() ) }, } NoteBlock { - props {}, + props { + instrument: NoteBlockInstrument = [ + NoteBlockInstrument::Harp, + NoteBlockInstrument::BaseDrum, + NoteBlockInstrument::Snare, + NoteBlockInstrument::Hat, + NoteBlockInstrument::Bass, + NoteBlockInstrument::Flute, + NoteBlockInstrument::Bell, + NoteBlockInstrument::Guitar, + NoteBlockInstrument::Chime, + NoteBlockInstrument::Xylophone + ], + note: u8 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24], + powered: bool = [true, false], + }, + data if instrument == NoteBlockInstrument::Harp && note == 0 && powered { Some(0) } else { None }, + offset Some(instrument.offset() * (25 * 2) + ((note as usize) << 1) + if powered { 0 } else { 1 }), model { ("minecraft", "noteblock") }, } Bed { props { + color: ColoredVariant = [ + ColoredVariant::White, + ColoredVariant::Orange, + ColoredVariant::Magenta, + ColoredVariant::LightBlue, + ColoredVariant::Yellow, + ColoredVariant::Lime, + ColoredVariant::Pink, + ColoredVariant::Gray, + ColoredVariant::Silver, + ColoredVariant::Cyan, + ColoredVariant::Purple, + ColoredVariant::Blue, + ColoredVariant::Brown, + ColoredVariant::Green, + ColoredVariant::Red, + ColoredVariant::Black + ], facing: Direction = [ Direction::North, Direction::South, @@ -602,9 +805,13 @@ define_blocks! { occupied: bool = [false, true], part: BedPart = [BedPart::Head, BedPart::Foot], }, - data Some(facing.horizontal_index() + data if color != ColoredVariant::White { None } else { Some(facing.horizontal_index() | (if occupied { 0x4 } else { 0x0 }) - | (if part == BedPart::Head { 0x8 } else { 0x0 })), + | (if part == BedPart::Head { 0x8 } else { 0x0 }))}, + offset Some(color.data() * (2 * 2 * 4) + + (facing.horizontal_offset() * (2 * 2)) + + (if occupied { 0 } else { 2 }) + + (if part == BedPart::Head { 0 } else { 1 })), material material::NON_SOLID, model { ("minecraft", "bed") }, variant format!("facing={},part={}", facing.as_string(), part.as_string()), @@ -623,6 +830,7 @@ define_blocks! { ], }, data Some(shape.data() | (if powered { 0x8 } else { 0x0 })), + offset Some(shape.data() + (if powered { 0 } else { 6 })), material material::NON_SOLID, model { ("minecraft", "golden_rail") }, variant format!("powered={},shape={}", powered, shape.as_string()), @@ -641,6 +849,7 @@ define_blocks! { ], }, data Some(shape.data() | (if powered { 0x8 } else { 0x0 })), + offset Some(shape.data() + (if powered { 0 } else { 6 })), material material::NON_SOLID, model { ("minecraft", "detector_rail") }, variant format!("powered={},shape={}", powered, shape.as_string()), @@ -659,6 +868,7 @@ define_blocks! { ], }, data Some(facing.index() | (if extended { 0x8 } else { 0x0 })), + offset Some(facing.offset() + (if extended { 0 } else { 6 })), material Material { should_cull_against: !extended, ..material::NON_SOLID @@ -687,8 +897,30 @@ define_blocks! { tint TintType::Grass, collision vec![], } + Seagrass { + props {}, + data None::, + offset Some(0), + material material::NON_SOLID, + model { ("minecraft", "seagrass") }, + collision vec![], + } + TallSeagrass { + props { + half: TallSeagrassHalf = [ + TallSeagrassHalf::Upper, + TallSeagrassHalf::Lower + ], + }, + data None::, + offset Some(half.offset()), + material material::NON_SOLID, + model { ("minecraft", "tall_seagrass") }, + collision vec![], + } DeadBush { props {}, + offset None, material material::NON_SOLID, model { ("minecraft", "dead_bush") }, collision vec![], @@ -706,6 +938,7 @@ define_blocks! { ], }, data Some(facing.index() | (if extended { 0x8 } else { 0x0 })), + offset Some(facing.offset() + (if extended { 0 } else { 6 })), material Material { should_cull_against: !extended, ..material::NON_SOLID @@ -728,6 +961,9 @@ define_blocks! { variant: PistonType = [PistonType::Normal, PistonType::Sticky], }, data if !short { Some(facing.index() | if variant == PistonType::Sticky { 0x8 } else { 0x0 })} else { None }, + offset Some(facing.offset() * 4 + + (if short { 0 } else { 2 }) + + (if variant == PistonType::Normal { 0 } else { 1 })), material material::NON_SOLID, model { ("minecraft", "piston_head") }, variant format!("facing={},short={},type={}", facing.as_string(), short, variant.as_string()), @@ -773,7 +1009,19 @@ define_blocks! { model { ("minecraft", format!("{}_wool", color.as_string()) ) }, } PistonExtension { - props {}, + props { + facing: Direction = [ + Direction::Up, + Direction::Down, + Direction::North, + Direction::South, + Direction::West, + Direction::East + ], + variant: PistonType = [PistonType::Normal, PistonType::Sticky], + }, + data if facing == Direction::Up && variant == PistonType::Normal { Some(0) } else { None }, + offset Some(facing.offset() * 2 + (if variant == PistonType::Normal { 0 } else { 1 })), material material::INVISIBLE, model { ("minecraft", "piston_extension") }, } @@ -831,7 +1079,7 @@ define_blocks! { variant: StoneSlabVariant = [ StoneSlabVariant::Stone, StoneSlabVariant::Sandstone, - StoneSlabVariant::Wood, + StoneSlabVariant::PetrifiedWood, StoneSlabVariant::Cobblestone, StoneSlabVariant::Brick, StoneSlabVariant::StoneBrick, @@ -853,6 +1101,7 @@ define_blocks! { Some(data) }, + offset None, model { ("minecraft", format!("{}_double_slab", variant.as_string()) ) }, variant if seamless { "all" } else { "normal" }, } @@ -862,7 +1111,7 @@ define_blocks! { variant: StoneSlabVariant = [ StoneSlabVariant::Stone, StoneSlabVariant::Sandstone, - StoneSlabVariant::Wood, + StoneSlabVariant::PetrifiedWood, StoneSlabVariant::Cobblestone, StoneSlabVariant::Brick, StoneSlabVariant::StoneBrick, @@ -871,6 +1120,7 @@ define_blocks! { ], }, data Some(variant.data() | (if half == BlockHalf::Top { 0x8 } else { 0x0 })), + offset None, material material::NON_SOLID, model { ("minecraft", format!("{}_slab", variant.as_string()) ) }, variant format!("half={}", half.as_string()), @@ -885,6 +1135,7 @@ define_blocks! { explode: bool = [false, true], }, data Some(if explode { 1 } else { 0 }), + offset Some(if explode { 0 } else { 1 }), model { ("minecraft", "tnt") }, } BookShelf { @@ -919,6 +1170,16 @@ define_blocks! { _ => unreachable!(), }) }, + offset { + Some(match facing { + Direction::Up => 0, + Direction::North => 1, + Direction::South => 2, + Direction::West => 3, + Direction::East => 4, + _ => unreachable!(), + }) + }, material Material { emitted_light: 14, ..material::NON_SOLID @@ -937,6 +1198,13 @@ define_blocks! { east: bool = [false, true], }, data if !up && !north && !south && !west && !east { Some(age as usize) } else { None }, + offset Some( + if west { 0 } else { 1<<0 } | + if up { 0 } else { 1<<1 } | + if south { 0 } else { 1<<2 } | + if north { 0 } else { 1<<3 } | + if east { 0 } else { 1<<4 } | + ((age as usize) << 5)), material Material { emitted_light: 15, ..material::NON_SOLID @@ -983,13 +1251,15 @@ define_blocks! { StairShape::OuterLeft, StairShape::OuterRight ], + waterlogged: bool = [true, false], }, - data stair_data(facing, half, shape), + data stair_data(facing, half, shape, waterlogged), + offset stair_offset(facing, half, shape, waterlogged), material material::NON_SOLID, model { ("minecraft", "oak_stairs") }, variant format!("facing={},half={},shape={}", facing.as_string(), half.as_string(), shape.as_string()), collision stair_collision(facing, shape, half), - update_state (world, pos) => Block::OakStairs{facing: facing, half: half, shape: update_stair_shape(world, pos, facing)}, + update_state (world, pos) => Block::OakStairs{facing, half, shape: update_stair_shape(world, pos, facing), waterlogged}, } Chest { props { @@ -999,8 +1269,17 @@ define_blocks! { Direction::West, Direction::East ], + type_: ChestType = [ + ChestType::Single, + ChestType::Left, + ChestType::Right + ], + waterlogged: bool = [true, false], }, - data Some(facing.index()), + data if type_ == ChestType::Single && !waterlogged { Some(facing.index()) } else { None }, + offset Some(if waterlogged { 0 } else { 1 } + + type_.offset() * 2 + + facing.horizontal_offset() * (2 * 3)), material material::NON_SOLID, model { ("minecraft", "chest") }, } @@ -1020,6 +1299,12 @@ define_blocks! { None } }, + offset Some( + west.offset() + + south.offset() * 3 + + (power as usize) * (3 * 3) + + north.offset() * (3 * 3 * 16) + + east.offset() * (3 * 3 * 16 * 3)), material material::NON_SOLID, model { ("minecraft", "redstone_wire") }, tint TintType::Color{r: ((255.0 / 30.0) * (f64::from(power)) + 14.0) as u8, g: 0, b: 0}, @@ -1082,8 +1367,10 @@ define_blocks! { Direction::West, Direction::East ], + lit: bool = [true, false], }, - data Some(facing.index()), + data if !lit { Some(facing.index()) } else { None }, + offset Some(if lit { 0 } else { 1 } + facing.horizontal_offset() * 2), model { ("minecraft", "furnace") }, variant format!("facing={}", facing.as_string()), } @@ -1097,6 +1384,7 @@ define_blocks! { ], }, data Some(facing.index()), + offset None, material Material { emitted_light: 13, ..material::SOLID @@ -1124,8 +1412,10 @@ define_blocks! { Rotation::SouthEast, Rotation::SouthSouthEast ], + waterlogged: bool = [true, false], }, - data Some(rotation.data()), + data if !waterlogged { Some(rotation.data()) } else { None }, + offset Some(rotation.data() * 2 + if waterlogged { 0 } else { 1 }), material material::INVISIBLE, model { ("minecraft", "standing_sign") }, collision vec![], @@ -1144,6 +1434,7 @@ define_blocks! { powered: bool = [false, true], }, data door_data(facing, half, hinge, open, powered), + offset door_offset(facing, half, hinge, open, powered), material material::NON_SOLID, model { ("minecraft", "wooden_door") }, variant format!("facing={},half={},hinge={},open={}", facing.as_string(), half.as_string(), hinge.as_string(), open), @@ -1161,8 +1452,10 @@ define_blocks! { Direction::West, Direction::East ], + waterlogged: bool = [true, false], }, - data Some(facing.index()), + data if !waterlogged { Some(facing.index()) } else { None }, + offset Some(if waterlogged { 0 } else { 1 } + facing.horizontal_offset() * 2), material material::NON_SOLID, model { ("minecraft", "ladder") }, variant format!("facing={}", facing.as_string()), @@ -1204,13 +1497,15 @@ define_blocks! { StairShape::OuterLeft, StairShape::OuterRight ], + waterlogged: bool = [true, false], }, - data stair_data(facing, half, shape), + data stair_data(facing, half, shape, waterlogged), + offset stair_offset(facing, half, shape, waterlogged), material material::NON_SOLID, model { ("minecraft", "stone_stairs") }, variant format!("facing={},half={},shape={}", facing.as_string(), half.as_string(), shape.as_string()), collision stair_collision(facing, shape, half), - update_state (world, pos) => Block::StoneStairs{facing: facing, half: half, shape: update_stair_shape(world, pos, facing)}, + update_state (world, pos) => Block::StoneStairs{facing, half, shape: update_stair_shape(world, pos, facing), waterlogged}, } WallSign { props { @@ -1220,8 +1515,10 @@ define_blocks! { Direction::West, Direction::East ], + waterlogged: bool = [true, false], }, - data Some(facing.index()), + data if !waterlogged { Some(facing.index()) } else { None }, + offset Some(if waterlogged { 0 } else { 1 } + facing.horizontal_offset() * 2), material material::INVISIBLE, model { ("minecraft", "wall_sign") }, variant format!("facing={}", facing.as_string()), @@ -1229,22 +1526,24 @@ define_blocks! { } Lever { props { - facing: LeverDirection = [ - LeverDirection::North, - LeverDirection::South, - LeverDirection::East, - LeverDirection::West, - LeverDirection::UpX, - LeverDirection::DownX, - LeverDirection::UpZ, - LeverDirection::DownZ + face: AttachedFace = [ + AttachedFace::Floor, + AttachedFace::Wall, + AttachedFace::Ceiling + ], + facing: Direction = [ + Direction::North, + Direction::South, + Direction::West, + Direction::East ], powered: bool = [false, true], }, - data Some(facing.data() | (if powered { 0x8 } else { 0x0 })), + data face.data_with_facing_and_powered(facing, powered), + offset Some(face.offset() * (4 * 2) + facing.horizontal_offset() * 2 + if powered { 0 } else { 1 }), material material::NON_SOLID, model { ("minecraft", "lever") }, - variant format!("facing={},powered={}", facing.as_string(), powered), + variant format!("facing={},powered={}", face.variant_with_facing(facing), powered), collision vec![], } StonePressurePlate { @@ -1252,6 +1551,7 @@ define_blocks! { powered: bool = [false, true], }, data Some(if powered { 1 } else { 0 }), + offset Some(if powered { 0 } else { 1 }), material material::NON_SOLID, model { ("minecraft", "stone_pressure_plate") }, variant format!("powered={}", powered), @@ -1271,6 +1571,7 @@ define_blocks! { powered: bool = [false, true], }, data door_data(facing, half, hinge, open, powered), + offset door_offset(facing, half, hinge, open, powered), material material::NON_SOLID, model { ("minecraft", "iron_door") }, variant format!("facing={},half={},hinge={},open={}", facing.as_string(), half.as_string(), hinge.as_string(), open), @@ -1282,20 +1583,34 @@ define_blocks! { } WoodenPressurePlate { props { + wood: TreeVariant = [ + TreeVariant::Oak, + TreeVariant::Spruce, + TreeVariant::Birch, + TreeVariant::Jungle, + TreeVariant::Acacia, + TreeVariant::DarkOak + ], powered: bool = [false, true], }, - data Some(if powered { 1 } else { 0 }), + data if wood == TreeVariant::Oak { Some(if powered { 1 } else { 0 }) } else { None }, + offset Some(wood.offset() * 2 + if powered { 0 } else { 1 }), material material::NON_SOLID, model { ("minecraft", "wooden_pressure_plate") }, variant format!("powered={}", powered), collision vec![], } RedstoneOre { - props {}, - model { ("minecraft", "redstone_ore") }, + props { + lit: bool = [true, false], + }, + data if !lit { Some(0) } else { None }, + offset Some(if lit { 0 } else { 1 }), + model { ("minecraft", if lit { "lit_redstone_ore" } else { "redstone_ore" }) }, } RedstoneOreLit { props {}, + offset None, material Material { emitted_light: 9, ..material::SOLID @@ -1322,12 +1637,13 @@ define_blocks! { _ => unreachable!(), }) }, + offset None, material material::NON_SOLID, model { ("minecraft", "unlit_redstone_torch") }, variant format!("facing={}", facing.as_string()), collision vec![], } - RedstoneTorch { + RedstoneTorchLit { props { facing: Direction = [ Direction::East, @@ -1347,6 +1663,7 @@ define_blocks! { _ => unreachable!(), }) }, + offset None, material Material { emitted_light: 7, ..material::NON_SOLID @@ -1355,30 +1672,57 @@ define_blocks! { variant format!("facing={}", facing.as_string()), collision vec![], } - StoneButton { + RedstoneTorchStanding { + props { + lit: bool = [true, false], + }, + data None::, + offset Some(if lit { 0 } else { 1 }), + material material::NON_SOLID, + model { ("minecraft", if lit { "redstone_torch" } else { "unlit_redstone_torch" }) }, + variant "facing=up", + collision vec![], + } + RedstoneTorchWall { props { facing: Direction = [ - Direction::Down, Direction::East, Direction::West, Direction::South, - Direction::North, - Direction::Up + Direction::North + ], + lit: bool = [true, false], + }, + data None::, + offset Some(if lit { 0 } else { 1 } + facing.horizontal_offset() * 2), + material Material { + emitted_light: 7, + ..material::NON_SOLID + }, + model { ("minecraft", if lit { "redstone_torch" } else { "unlit_redstone_torch" }) }, + variant format!("facing={}", facing.as_string()), + collision vec![], + } + StoneButton { + props { + face: AttachedFace = [ + AttachedFace::Floor, + AttachedFace::Wall, + AttachedFace::Ceiling + ], + facing: Direction = [ + Direction::East, + Direction::West, + Direction::South, + Direction::North ], powered: bool = [false, true], }, - data Some(match facing { - Direction::Down => 0, - Direction::East => 1, - Direction::West => 2, - Direction::South => 3, - Direction::North => 4, - Direction::Up => 5, - _ => unreachable!(), - } | (if powered { 0x8 } else { 0x0 })), + data face.data_with_facing_and_powered(facing, powered), + offset Some(face.offset() * (4 * 2) + facing.horizontal_offset() * 2 + if powered { 0 } else { 1 }), material material::NON_SOLID, model { ("minecraft", "stone_button") }, - variant format!("facing={},powered={}", facing.as_string(), powered), + variant format!("facing={},powered={}", face.variant_with_facing(facing), powered), } SnowLayer { props { @@ -1436,6 +1780,7 @@ define_blocks! { has_record: bool = [false, true], }, data Some(if has_record { 1 } else { 0 }), + offset Some(if has_record { 0 } else { 1 }), model { ("minecraft", "jukebox") }, } Fence { @@ -1444,14 +1789,20 @@ define_blocks! { south: bool = [false, true], west: bool = [false, true], east: bool = [false, true], + waterlogged: bool = [false, true], }, - data if !north && !south && !east && !west { Some(0) } else { None }, + data if !north && !south && !east && !west && !waterlogged { Some(0) } else { None }, + offset Some(if west { 0 } else { 1<<0 } + + if waterlogged { 0 } else { 1<<1 } + + if south { 0 } else { 1<<2 } + + if north { 0 } else { 1<<3 } + + if east { 0 } else { 1<<4 }), material material::NON_SOLID, model { ("minecraft", "fence") }, collision fence_collision(north, south, west, east), update_state (world, pos) => { let (north, south, west, east) = can_connect_sides(world, pos, &can_connect_fence); - Block::Fence{north: north, south: south, west: west, east: east} + Block::Fence{north, south, west, east, waterlogged} }, multipart (key, val) => match key { "north" => north == (val == "true"), @@ -1461,7 +1812,7 @@ define_blocks! { _ => false, }, } - Pumpkin { + PumpkinFace { props { facing: Direction = [ Direction::North, @@ -1472,9 +1823,16 @@ define_blocks! { without_face: bool = [false, true], }, data Some(facing.horizontal_index() | (if without_face { 0x4 } else { 0x0 })), + offset None, model { ("minecraft", "pumpkin") }, variant format!("facing={}", facing.as_string()), } + Pumpkin { + props {}, + data None::, + offset Some(0), + model { ("minecraft", "pumpkin") }, + } Netherrack { props {}, model { ("minecraft", "netherrack") }, @@ -1501,6 +1859,7 @@ define_blocks! { axis: Axis = [Axis::X, Axis::Z], }, data Some(axis.index()), + offset Some(axis.index() - 1), material Material { emitted_light: 11, ..material::TRANSPARENT @@ -1509,6 +1868,24 @@ define_blocks! { variant format!("axis={}", axis.as_string()), collision vec![], } + PumpkinCarved { + props { + facing: Direction = [ + Direction::North, + Direction::South, + Direction::West, + Direction::East + ], + }, + data None::, + offset Some(facing.horizontal_offset()), + material Material { + emitted_light: 15, + ..material::SOLID + }, + model { ("minecraft", "carved_pumpkin") }, + variant format!("facing={}", facing.as_string()), + } PumpkinLit { props { facing: Direction = [ @@ -1520,6 +1897,7 @@ define_blocks! { without_face: bool = [false, true], }, data Some(facing.horizontal_index() | (if without_face { 0x4 } else { 0x0 })), + offset if without_face { None } else { Some(facing.horizontal_offset()) }, material Material { emitted_light: 15, ..material::SOLID @@ -1540,7 +1918,7 @@ define_blocks! { Point3::new(1.0 - (1.0/16.0), 0.5, 1.0 - (1.0/16.0)) )], } - RepeaterUnpowered { + Repeater { props { delay: u8 = [1, 2, 3, 4], facing: Direction = [ @@ -1550,16 +1928,21 @@ define_blocks! { Direction::East ], locked: bool = [false, true], + powered: bool = [true, false], }, - data if !locked { Some(facing.horizontal_index() | (delay as usize - 1) << 2) } else { None }, + data if powered { None } else { if !locked { Some(facing.horizontal_index() | (delay as usize - 1) << 2) } else { None } }, + offset Some(if powered { 0 } else { 1<<0 } + + if locked { 0 } else { 1<<1 } + + facing.horizontal_offset() * (2 * 2) + + ((delay - 1) as usize) * (2 * 2 * 4)), material material::NON_SOLID, - model { ("minecraft", "unpowered_repeater") }, + model { ("minecraft", if powered { "powered_repeater" } else { "unpowered_repeater" }) }, variant format!("delay={},facing={},locked={}", delay, facing.as_string(), locked), collision vec![Aabb3::new( Point3::new(0.0, 0.0, 0.0), Point3::new(1.0, 1.0/8.0, 1.0) )], - update_state (world, pos) => RepeaterUnpowered{delay: delay, facing: facing, locked: update_repeater_state(world, pos, facing)}, + update_state (world, pos) => Repeater{delay, facing, locked: update_repeater_state(world, pos, facing), powered}, } RepeaterPowered { props { @@ -1573,6 +1956,7 @@ define_blocks! { locked: bool = [false, true], }, data if !locked { Some(facing.horizontal_index() | (delay as usize - 1) << 2) } else { None }, + offset None, material material::NON_SOLID, model { ("minecraft", "powered_repeater") }, variant format!("delay={},facing={},locked={}", delay, facing.as_string(), locked), @@ -1617,14 +2001,30 @@ define_blocks! { ], half: BlockHalf = [BlockHalf::Top, BlockHalf::Bottom], open: bool = [false, true], + waterlogged: bool = [true, false], + powered: bool = [true, false], + wood: TreeVariant = [ + TreeVariant::Oak, + TreeVariant::Spruce, + TreeVariant::Birch, + TreeVariant::Jungle, + TreeVariant::Acacia, + TreeVariant::DarkOak + ], }, - data Some(match facing { + data if waterlogged || powered || wood != TreeVariant::Oak { None } else { Some(match facing { Direction::North => 0, Direction::South => 1, Direction::West => 2, Direction::East => 3, _ => unreachable!(), - } | (if open { 0x4 } else { 0x0 }) | (if half == BlockHalf::Top { 0x8 } else { 0x0 })), + } | (if open { 0x4 } else { 0x0 }) | (if half == BlockHalf::Top { 0x8 } else { 0x0 }))}, + offset Some(if waterlogged { 0 } else { 1<<0 } + + if powered { 0 } else { 1<<1 } + + if open { 0 } else { 1<<2 } + + if half == BlockHalf::Top { 0 } else { 1<<3 } + + facing.horizontal_offset() * (2 * 2 * 2 * 2) + + wood.offset() * (2 * 2 * 2 * 2 * 4)), material material::NON_SOLID, model { ("minecraft", "trapdoor") }, variant format!("facing={},half={},open={}", facing.as_string(), half.as_string(), open), @@ -1658,47 +2058,47 @@ define_blocks! { } BrownMushroomBlock { props { - variant: MushroomVariant = [ - MushroomVariant::East, - MushroomVariant::North, - MushroomVariant::NorthEast, - MushroomVariant::NorthWest, - MushroomVariant::South, - MushroomVariant::SouthEast, - MushroomVariant::SouthWest, - MushroomVariant::West, - MushroomVariant::Center, - MushroomVariant::Stem, - MushroomVariant::AllInside, - MushroomVariant::AllOutside, - MushroomVariant::AllStem - ], + is_stem: bool = [true, false], + west: bool = [true, false], + up: bool = [true, false], + south: bool = [true, false], + north: bool = [true, false], + east: bool = [true, false], + down: bool = [true, false], }, - data Some(variant.data()), + data mushroom_block_data(is_stem, west, up, south, north, east, down), + offset mushroom_block_offset(is_stem, west, up, south, north, east, down), model { ("minecraft", "brown_mushroom_block") }, - variant format!("variant={}", variant.as_string()), + variant format!("variant={}", mushroom_block_variant(is_stem, west, up, south, north, east, down)), } RedMushroomBlock { props { - variant: MushroomVariant = [ - MushroomVariant::East, - MushroomVariant::North, - MushroomVariant::NorthEast, - MushroomVariant::NorthWest, - MushroomVariant::South, - MushroomVariant::SouthEast, - MushroomVariant::SouthWest, - MushroomVariant::West, - MushroomVariant::Center, - MushroomVariant::Stem, - MushroomVariant::AllInside, - MushroomVariant::AllOutside, - MushroomVariant::AllStem - ], + is_stem: bool = [true, false], + west: bool = [true, false], + up: bool = [true, false], + south: bool = [true, false], + north: bool = [true, false], + east: bool = [true, false], + down: bool = [true, false], }, - data Some(variant.data()), + data mushroom_block_data(is_stem, west, up, south, north, east, down), + offset mushroom_block_offset(is_stem, west, up, south, north, east, down), model { ("minecraft", "red_mushroom_block") }, - variant format!("variant={}", variant.as_string()), + variant format!("variant={}", mushroom_block_variant(is_stem, west, up, south, north, east, down)), + } + MushroomStem { + props { + west: bool = [true, false], + up: bool = [true, false], + south: bool = [true, false], + north: bool = [true, false], + east: bool = [true, false], + down: bool = [true, false], + }, + data None::, + offset mushroom_block_offset(false, west, up, south, north, east, down), + model { ("minecraft", "mushroom_stem") }, + variant format!("variant=all_stem"), } IronBars { props { @@ -1706,8 +2106,14 @@ define_blocks! { south: bool = [false, true], west: bool = [false, true], east: bool = [false, true], + waterlogged: bool = [true, false], }, - data if !north && !south && !west && !east { Some(0) } else { None }, + data if !waterlogged && !north && !south && !west && !east { Some(0) } else { None }, + offset Some(if west { 0 } else { 1<<0 } + + if waterlogged { 0 } else { 1<<1 } + + if south { 0 } else { 1<<2 } + + if north { 0 } else { 1<<3 } + + if east { 0 } else { 1<<4 }), material material::NON_SOLID, model { ("minecraft", "iron_bars") }, collision pane_collision(north, south, east, west), @@ -1718,7 +2124,7 @@ define_blocks! { }; let (north, south, west, east) = can_connect_sides(world, pos, &f); - Block::IronBars{north: north, south: south, west: west, east: east} + Block::IronBars{north, south, west, east, waterlogged} }, multipart (key, val) => match key { "north" => north == (val == "true"), @@ -1734,14 +2140,20 @@ define_blocks! { south: bool = [false, true], west: bool = [false, true], east: bool = [false, true], + waterlogged: bool = [true, false], }, - data if !north && !south && !west && !east { Some(0) } else { None }, + data if !waterlogged && !north && !south && !west && !east { Some(0) } else { None }, + offset Some(if west { 0 } else { 1<<0 } + + if waterlogged { 0 } else { 1<<1 } + + if south { 0 } else { 1<<2 } + + if north { 0 } else { 1<<3 } + + if east { 0 } else { 1<<4 }), material material::NON_SOLID, model { ("minecraft", "glass_pane") }, collision pane_collision(north, south, east, west), update_state (world, pos) => { let (north, south, west, east) = can_connect_sides(world, pos, &can_connect_glasspane); - Block::GlassPane{north: north, south: south, west: west, east: east} + Block::GlassPane{north, south, west, east, waterlogged} }, multipart (key, val) => match key { "north" => north == (val == "true"), @@ -1755,6 +2167,62 @@ define_blocks! { props {}, model { ("minecraft", "melon_block") }, } + AttachedPumpkinStem { + props { + facing: Direction = [ + Direction::North, + Direction::South, + Direction::West, + Direction::East + ], + }, + data None::, + offset Some(facing.horizontal_offset()), + material material::NON_SOLID, + model { ("minecraft", "pumpkin_stem") }, + variant format!("facing={}", facing.as_string()), + collision vec![], + update_state (world, pos) => { + let facing = match (world.get_block(pos.shift(Direction::East)), world.get_block(pos.shift(Direction::West)), + world.get_block(pos.shift(Direction::North)), world.get_block(pos.shift(Direction::South))) { + (Block::Pumpkin{ .. }, _, _, _) => Direction::East, + (_, Block::Pumpkin{ .. }, _, _) => Direction::West, + (_, _, Block::Pumpkin{ .. }, _) => Direction::North, + (_, _, _, Block::Pumpkin{ .. }) => Direction::South, + _ => Direction::Up, + }; + + Block::AttachedPumpkinStem{facing} + }, + } + AttachedMelonStem { + props { + facing: Direction = [ + Direction::North, + Direction::South, + Direction::West, + Direction::East + ], + }, + data None::, + offset Some(facing.horizontal_offset()), + material material::NON_SOLID, + model { ("minecraft", "melon_stem") }, + variant format!("facing={}", facing.as_string()), + collision vec![], + update_state (world, pos) => { + let facing = match (world.get_block(pos.shift(Direction::East)), world.get_block(pos.shift(Direction::West)), + world.get_block(pos.shift(Direction::North)), world.get_block(pos.shift(Direction::South))) { + (Block::MelonBlock{ .. }, _, _, _) => Direction::East, + (_, Block::MelonBlock{ .. }, _, _) => Direction::West, + (_, _, Block::MelonBlock{ .. }, _) => Direction::North, + (_, _, _, Block::MelonBlock{ .. }) => Direction::South, + _ => Direction::Up, + }; + + Block::AttachedMelonStem{facing} + }, + } PumpkinStem { props { age: u8 = [0, 1, 2, 3, 4, 5, 6, 7], @@ -1843,6 +2311,11 @@ define_blocks! { } else { None }, + offset Some(if west { 0 } else { 1<<0 } + + if up { 0 } else { 1<<1 } + + if south { 0 } else { 1<<2 } + + if north { 0 } else { 1<<3 } + + if east { 0 } else { 1<<4 }), material material::NON_SOLID, model { ("minecraft", "vine") }, variant format!("east={},north={},south={},up={},west={}", east, north, south, up, west), @@ -1867,6 +2340,7 @@ define_blocks! { powered: bool = [false, true], }, data fence_gate_data(facing, in_wall, open, powered), + offset fence_gate_offset(facing, in_wall, open, powered), material material::NON_SOLID, model { ("minecraft", "fence_gate") }, variant format!("facing={},in_wall={},open={}", facing.as_string(), in_wall, open), @@ -1894,13 +2368,15 @@ define_blocks! { StairShape::OuterLeft, StairShape::OuterRight ], + waterlogged: bool = [true, false], }, - data stair_data(facing, half, shape), + data stair_data(facing, half, shape, waterlogged), + offset stair_offset(facing, half, shape, waterlogged), material material::NON_SOLID, model { ("minecraft", "brick_stairs") }, variant format!("facing={},half={},shape={}", facing.as_string(), half.as_string(), shape.as_string()), collision stair_collision(facing, shape, half), - update_state (world, pos) => Block::BrickStairs{facing: facing, half: half, shape: update_stair_shape(world, pos, facing)}, + update_state (world, pos) => Block::BrickStairs{facing, half, shape: update_stair_shape(world, pos, facing), waterlogged}, } StoneBrickStairs { props { @@ -1918,19 +2394,22 @@ define_blocks! { StairShape::OuterLeft, StairShape::OuterRight ], + waterlogged: bool = [true, false], }, - data stair_data(facing, half, shape), + data stair_data(facing, half, shape, waterlogged), + offset stair_offset(facing, half, shape, waterlogged), material material::NON_SOLID, model { ("minecraft", "stone_brick_stairs") }, variant format!("facing={},half={},shape={}", facing.as_string(), half.as_string(), shape.as_string()), collision stair_collision(facing, shape, half), - update_state (world, pos) => Block::StoneBrickStairs{facing: facing, half: half, shape: update_stair_shape(world, pos, facing)}, + update_state (world, pos) => Block::StoneBrickStairs{facing, half, shape: update_stair_shape(world, pos, facing), waterlogged}, } Mycelium { props { snowy: bool = [false, true], }, data if snowy { None } else { Some(0) }, + offset Some(if snowy { 0 } else { 1 }), material material::SOLID, model { ("minecraft", "mycelium") }, variant format!("snowy={}", snowy), @@ -1956,8 +2435,14 @@ define_blocks! { south: bool = [false, true], west: bool = [false, true], east: bool = [false, true], + waterlogged: bool = [true, false], }, - data if !north && !south && !west && !east { Some(0) } else { None }, + data if !north && !south && !west && !east && !waterlogged { Some(0) } else { None }, + offset Some(if west { 0 } else { 1<<0 } + + if waterlogged { 0 } else { 1<<1 } + + if south { 0 } else { 1<<2 } + + if north { 0 } else { 1<<3 } + + if east { 0 } else { 1<<4 }), material material::NON_SOLID, model { ("minecraft", "nether_brick_fence") }, collision fence_collision(north, south, west, east), @@ -1974,7 +2459,7 @@ define_blocks! { }; let (north, south, west, east) = can_connect_sides(world, pos, &f); - Block::NetherBrickFence{north: north, south: south, west: west, east: east} + Block::NetherBrickFence{north, south, west, east, waterlogged} }, multipart (key, val) => match key { "north" => north == (val == "true"), @@ -2000,13 +2485,15 @@ define_blocks! { StairShape::OuterLeft, StairShape::OuterRight ], + waterlogged: bool = [true, false], }, - data stair_data(facing, half, shape), + data stair_data(facing, half, shape, waterlogged), + offset stair_offset(facing, half, shape, waterlogged), material material::NON_SOLID, model { ("minecraft", "nether_brick_stairs") }, variant format!("facing={},half={},shape={}", facing.as_string(), half.as_string(), shape.as_string()), collision stair_collision(facing, shape, half), - update_state (world, pos) => Block::NetherBrickStairs{facing: facing, half: half, shape: update_stair_shape(world, pos, facing)}, + update_state (world, pos) => Block::NetherBrickStairs{facing, half, shape: update_stair_shape(world, pos, facing), waterlogged}, } NetherWart { props { @@ -2036,6 +2523,9 @@ define_blocks! { data Some((if has_bottle_0 { 0x1 } else { 0x0 }) | (if has_bottle_1 { 0x2 } else { 0x0 }) | (if has_bottle_2 { 0x4 } else { 0x0 })), + offset Some(if has_bottle_0 { 0 } else { 1<<0 } + + if has_bottle_1 { 0 } else { 1<<1 } + + if has_bottle_2 { 0 } else { 1<<2 }), material Material { emitted_light: 1, ..material::NON_SOLID @@ -2077,6 +2567,7 @@ define_blocks! { ], }, data Some(facing.horizontal_index() | (if eye { 0x4 } else { 0x0 })), + offset Some(facing.horizontal_offset() + (if eye { 0 } else { 4 })), material Material { emitted_light: 1, ..material::NON_SOLID @@ -2139,6 +2630,7 @@ define_blocks! { ], }, data Some(variant.data()), + offset None, model { ("minecraft", format!("{}_double_slab", variant.as_string()) ) }, } WoodenSlab { @@ -2154,6 +2646,7 @@ define_blocks! { ], }, data Some(variant.data() | (if half == BlockHalf::Top { 0x8 } else { 0x0 })), + offset None, material material::NON_SOLID, model { ("minecraft", format!("{}_slab", variant.as_string()) ) }, variant format!("half={}", half.as_string()), @@ -2170,6 +2663,7 @@ define_blocks! { ], }, data Some(facing.horizontal_index() | ((age as usize) << 2)), + offset Some(facing.horizontal_offset() + ((age as usize) * 4)), material material::NON_SOLID, model { ("minecraft", "cocoa") }, variant format!("age={},facing={}", age, facing.as_string()), @@ -2208,13 +2702,15 @@ define_blocks! { StairShape::OuterLeft, StairShape::OuterRight ], + waterlogged: bool = [true, false], }, - data stair_data(facing, half, shape), + data stair_data(facing, half, shape, waterlogged), + offset stair_offset(facing, half, shape, waterlogged), material material::NON_SOLID, model { ("minecraft", "sandstone_stairs") }, variant format!("facing={},half={},shape={}", facing.as_string(), half.as_string(), shape.as_string()), collision stair_collision(facing, shape, half), - update_state (world, pos) => Block::SandstoneStairs{facing: facing, half: half, shape: update_stair_shape(world, pos, facing)}, + update_state (world, pos) => Block::SandstoneStairs{facing, half, shape: update_stair_shape(world, pos, facing), waterlogged}, } EmeraldOre { props {}, @@ -2229,8 +2725,10 @@ define_blocks! { Direction::West, Direction::East ], + waterlogged: bool = [true, false], }, - data Some(facing.index()), + data if waterlogged { None } else { Some(facing.index()) }, + offset Some(if waterlogged { 0 } else { 1 } + facing.horizontal_offset() * 2), material Material { emitted_light: 7, ..material::NON_SOLID @@ -2256,6 +2754,9 @@ define_blocks! { data Some(facing.horizontal_index() | (if attached { 0x4 } else { 0x0 }) | (if powered { 0x8 } else { 0x0 })), + offset Some(if powered { 0 } else { 1 } + + facing.horizontal_offset() * 2 + + if attached { 0 } else { 2 * 4 }), material material::NON_SOLID, model { ("minecraft", "tripwire_hook") }, variant format!("attached={},facing={},powered={}", attached, facing.as_string(), powered), @@ -2280,6 +2781,17 @@ define_blocks! { } else { None }, + offset if mojang_cant_even { + None + } else { + Some(if west { 0 } else { 1<<0 } + + if south { 0 } else { 1<<1 } + + if powered { 0 } else { 1<<2 } + + if north { 0 } else { 1<<3 } + + if east { 0 } else { 1<<4 } + + if disarmed { 0 } else { 1<<5 } + + if attached { 0 } else { 1<<6 }) + }, material material::TRANSPARENT, model { ("minecraft", "tripwire") }, variant format!("attached={},east={},north={},south={},west={}", attached, east, north, south, west), @@ -2325,13 +2837,15 @@ define_blocks! { StairShape::OuterLeft, StairShape::OuterRight ], + waterlogged: bool = [true, false], }, - data stair_data(facing, half, shape), + data stair_data(facing, half, shape, waterlogged), + offset stair_offset(facing, half, shape, waterlogged), material material::NON_SOLID, model { ("minecraft", "spruce_stairs") }, variant format!("facing={},half={},shape={}", facing.as_string(), half.as_string(), shape.as_string()), collision stair_collision(facing, shape, half), - update_state (world, pos) => Block::SpruceStairs{facing: facing, half: half, shape: update_stair_shape(world, pos, facing)}, + update_state (world, pos) => Block::SpruceStairs{facing, half, shape: update_stair_shape(world, pos, facing), waterlogged}, } BirchStairs { props { @@ -2349,13 +2863,15 @@ define_blocks! { StairShape::OuterLeft, StairShape::OuterRight ], + waterlogged: bool = [true, false], }, - data stair_data(facing, half, shape), + data stair_data(facing, half, shape, waterlogged), + offset stair_offset(facing, half, shape, waterlogged), material material::NON_SOLID, model { ("minecraft", "birch_stairs") }, variant format!("facing={},half={},shape={}", facing.as_string(), half.as_string(), shape.as_string()), collision stair_collision(facing, shape, half), - update_state (world, pos) => Block::BirchStairs{facing: facing, half: half, shape: update_stair_shape(world, pos, facing)}, + update_state (world, pos) => Block::BirchStairs{facing, half, shape: update_stair_shape(world, pos, facing), waterlogged}, } JungleStairs { props { @@ -2373,13 +2889,15 @@ define_blocks! { StairShape::OuterLeft, StairShape::OuterRight ], + waterlogged: bool = [true, false], }, - data stair_data(facing, half, shape), + data stair_data(facing, half, shape, waterlogged), + offset stair_offset(facing, half, shape, waterlogged), material material::NON_SOLID, model { ("minecraft", "jungle_stairs") }, variant format!("facing={},half={},shape={}", facing.as_string(), half.as_string(), shape.as_string()), collision stair_collision(facing, shape, half), - update_state (world, pos) => Block::JungleStairs{facing: facing, half: half, shape: update_stair_shape(world, pos, facing)}, + update_state (world, pos) => Block::JungleStairs{facing, half, shape: update_stair_shape(world, pos, facing), waterlogged}, } CommandBlock { props { @@ -2394,6 +2912,7 @@ define_blocks! { ], }, data Some(facing.index() | (if conditional { 0x8 } else { 0x0 })), + offset Some(facing.offset() + (if conditional { 0 } else { 6 })), model { ("minecraft", "command_block") }, variant format!("conditional={},facing={}", conditional, facing.as_string()), } @@ -2416,8 +2935,16 @@ define_blocks! { CobblestoneWallVariant::Normal, CobblestoneWallVariant::Mossy ], + waterlogged: bool = [true, false], }, - data if !north && !south && !east && !west && !up { Some(variant.data()) } else { None }, + data if !north && !south && !east && !west && !up && !waterlogged { Some(variant.data()) } else { None }, + offset Some(if west { 0 } else { 1<<0 } + + if waterlogged { 0 } else { 1<<1 } + + if up { 0 } else { 1<<2 } + + if south { 0 } else { 1<<3 } + + if north { 0 } else { 1<<4 } + + if east { 0 } else { 1<<5 } + + if variant == CobblestoneWallVariant::Normal { 0 } else { 1<<6 }), material material::NON_SOLID, model { ("minecraft", format!("{}_wall", variant.as_string())) }, update_state (world, pos) => { @@ -2437,7 +2964,7 @@ define_blocks! { Block::Air{..} => true, _ => false, }) || !((north && south && !west && !east) || (!north && !south && west && east)); - Block::CobblestoneWall{up: up, north: north, south: south, west: west, east: east, variant: variant} + Block::CobblestoneWall{up, north, south, west, east, variant, waterlogged} }, multipart (key, val) => match key { "up" => up == (val == "true"), @@ -2464,7 +2991,7 @@ define_blocks! { FlowerPotVariant::DeadBush, FlowerPotVariant::Fern, FlowerPotVariant::AcaciaSapling, - FlowerPotVariant::DarkOak, + FlowerPotVariant::DarkOakSapling, FlowerPotVariant::BlueOrchid, FlowerPotVariant::Allium, FlowerPotVariant::AzureBluet, @@ -2472,12 +2999,12 @@ define_blocks! { FlowerPotVariant::OrangeTulip, FlowerPotVariant::WhiteTulip, FlowerPotVariant::PinkTulip, - FlowerPotVariant::Oxeye, - FlowerPotVariant::Dandelion + FlowerPotVariant::Oxeye ], legacy_data: u8 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], }, data if contents == FlowerPotVariant::Empty { Some(legacy_data as usize) } else { None }, + offset if legacy_data != 0 { None } else { Some(contents.offset()) }, material material::NON_SOLID, model { ("minecraft", "flower_pot") }, } @@ -2503,30 +3030,34 @@ define_blocks! { } WoodenButton { props { + face: AttachedFace = [ + AttachedFace::Floor, + AttachedFace::Wall, + AttachedFace::Ceiling + ], facing: Direction = [ - Direction::Down, Direction::East, Direction::West, Direction::South, - Direction::North, - Direction::Up + Direction::North ], powered: bool = [false, true], + variant: TreeVariant = [ + TreeVariant::Oak, + TreeVariant::Spruce, + TreeVariant::Birch, + TreeVariant::Jungle, + TreeVariant::Acacia, + TreeVariant::DarkOak + ], }, - data Some(match facing { - Direction::Down => 0, - Direction::East => 1, - Direction::West => 2, - Direction::South => 3, - Direction::North => 4, - Direction::Up => 5, - _ => unreachable!(), - } | (if powered { 0x8 } else { 0x0 })), + data if variant == TreeVariant::Oak { face.data_with_facing_and_powered(facing, powered) } else { None }, + offset Some(variant.offset() * (3 * 4 * 2) + face.offset() * (4 * 2) + facing.horizontal_offset() * 2 + if powered { 0 } else { 1 }), material material::NON_SOLID, model { ("minecraft", "wooden_button") }, - variant format!("facing={},powered={}", facing.as_string(), powered), + variant format!("facing={},powered={}", face.variant_with_facing(facing), powered), } - Skull { + SkullSkeletonWall { props { facing: Direction = [ Direction::Up, @@ -2538,6 +3069,7 @@ define_blocks! { nodrop: bool = [false, true], }, data if !nodrop { Some(facing.index()) } else { None }, + offset if !nodrop && facing != Direction::Up { Some(facing.horizontal_offset()) } else { None }, material material::NON_SOLID, model { ("minecraft", "skull") }, variant format!("facing={},nodrop={}", facing.as_string(), nodrop), @@ -2557,6 +3089,161 @@ define_blocks! { )] }, } + SkullSkeleton + { + props { + rotation: u8 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + }, + data None::, + offset Some(rotation as usize), + material material::NON_SOLID, + model { ("minecraft", "skull") }, + collision { + let (min_x, min_y, min_z, max_x, max_y, max_z) = (0.25, 0.0, 0.25, 0.75, 0.5, 0.75); + + vec![Aabb3::new( + Point3::new(min_x, min_y, min_z), + Point3::new(max_x, max_y, max_z) + )] + }, + } + SkullWitherSkeletonWall { + props { + facing: Direction = [ + Direction::North, + Direction::South, + Direction::West, + Direction::East + ], + }, + data None::, + offset Some(facing.horizontal_offset()), + material material::NON_SOLID, + model { ("minecraft", "skull") }, + collision { + let (min_x, min_y, min_z, max_x, max_y, max_z) = match facing { + Direction::North => (0.25, 0.25, 0.5, 0.75, 0.75, 1.0), + Direction::South => (0.25, 0.25, 0.0, 0.75, 0.75, 0.5), + Direction::West => (0.5, 0.25, 0.25, 1.0, 0.75, 0.75), + Direction::East => (0.0, 0.25, 0.25, 0.5, 0.75, 0.75), + _ => unreachable!(), + }; + + vec![Aabb3::new( + Point3::new(min_x, min_y, min_z), + Point3::new(max_x, max_y, max_z) + )] + }, + } + SkullWitherSkeleton { + props { + rotation: u8 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + }, + data None::, + offset Some(rotation as usize), + material material::NON_SOLID, + model { ("minecraft", "skull") }, + collision { + let (min_x, min_y, min_z, max_x, max_y, max_z) = (0.25, 0.0, 0.25, 0.75, 0.5, 0.75); + + vec![Aabb3::new( + Point3::new(min_x, min_y, min_z), + Point3::new(max_x, max_y, max_z) + )] + }, + } + ZombieWallHead { + props { + facing: Direction = [ + Direction::North, + Direction::South, + Direction::West, + Direction::East + ], + }, + data None::, + offset Some(facing.horizontal_offset()), + material material::NON_SOLID, + model { ("minecraft", "zombie_wall_head") }, + } + ZombieHead { + props { + rotation: u8 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + }, + data None::, + offset Some(rotation as usize), + material material::NON_SOLID, + model { ("minecraft", "zombie_head") }, + } + PlayerWallHead { + props { + facing: Direction = [ + Direction::North, + Direction::South, + Direction::West, + Direction::East + ], + }, + data None::, + offset Some(facing.horizontal_offset()), + material material::NON_SOLID, + model { ("minecraft", "player_wall_head") }, + } + PlayerHead { + props { + rotation: u8 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + }, + data None::, + offset Some(rotation as usize), + material material::NON_SOLID, + model { ("minecraft", "player_head") }, + } + CreeperWallHead { + props { + facing: Direction = [ + Direction::North, + Direction::South, + Direction::West, + Direction::East + ], + }, + data None::, + offset Some(facing.horizontal_offset()), + material material::NON_SOLID, + model { ("minecraft", "creeper_wall_head") }, + } + CreeperHead { + props { + rotation: u8 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + }, + data None::, + offset Some(rotation as usize), + material material::NON_SOLID, + model { ("minecraft", "creeper_head") }, + } + DragonWallHead { + props { + facing: Direction = [ + Direction::North, + Direction::South, + Direction::West, + Direction::East + ], + }, + data None::, + offset Some(facing.horizontal_offset()), + material material::NON_SOLID, + model { ("minecraft", "dragon_wall_head") }, + } + DragonHead { + props { + rotation: u8 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + }, + data None::, + offset Some(rotation as usize), + material material::NON_SOLID, + model { ("minecraft", "dragon_head") }, + } Anvil { props { damage: u8 = [0, 1, 2], @@ -2568,6 +3255,7 @@ define_blocks! { ], }, data Some(facing.horizontal_index() | (match damage { 0 => 0x0, 1 => 0x4, 2 => 0x8, _ => unreachable!() })), + offset Some(facing.horizontal_offset() + (damage as usize) * 4), material material::NON_SOLID, model { ("minecraft", "anvil") }, variant format!("damage={},facing={}", damage, facing.as_string()), @@ -2591,8 +3279,17 @@ define_blocks! { Direction::West, Direction::East ], + type_: ChestType = [ + ChestType::Single, + ChestType::Left, + ChestType::Right + ], + waterlogged: bool = [true, false], }, - data Some(facing.index()), + data if type_ == ChestType::Single && !waterlogged { Some(facing.index()) } else { None }, + offset Some(if waterlogged { 0 } else { 1 } + + type_.offset() * 2 + + facing.horizontal_offset() * (2 * 3)), material material::NON_SOLID, model { ("minecraft", "trapped_chest") }, variant format!("facing={}", facing.as_string()), @@ -2635,6 +3332,9 @@ define_blocks! { data Some(facing.horizontal_index() | (if mode == ComparatorMode::Subtract { 0x4 } else { 0x0 }) | (if powered { 0x8 } else { 0x0 })), + offset Some(if powered { 0 } else { 1<<0 } + + if mode == ComparatorMode::Compare { 0 } else { 1<<1 } + + facing.horizontal_offset() * (1<<2)), material material::NON_SOLID, model { ("minecraft", "unpowered_comparator") }, variant format!("facing={},mode={},powered={}", facing.as_string(), mode.as_string(), powered), @@ -2657,6 +3357,7 @@ define_blocks! { data Some(facing.horizontal_index() | (if mode == ComparatorMode::Subtract { 0x4 } else { 0x0 }) | (if powered { 0x8 } else { 0x0 })), + offset None, material material::NON_SOLID, model { ("minecraft", "powered_comparator") }, variant format!("facing={},mode={},powered={}", facing.as_string(), mode.as_string(), powered), @@ -2668,8 +3369,10 @@ define_blocks! { DaylightDetector { props { power: u8 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + inverted: bool = [true, false], }, - data Some(power as usize), + data if inverted { None } else { Some(power as usize) }, + offset Some((power as usize) + if inverted { 0 } else { 16 }), material material::NON_SOLID, model { ("minecraft", "daylight_detector") }, variant format!("power={}", power), @@ -2698,6 +3401,14 @@ define_blocks! { ], }, data Some(facing.index() | (if enabled { 0x8 } else { 0x0 })), + offset Some(match facing { + Direction::Down => 0, + Direction::North => 1, + Direction::South => 2, + Direction::West => 3, + Direction::East => 4, + _ => unreachable!(), + } + if enabled { 0 } else { 5 }), material material::NON_SOLID, model { ("minecraft", "hopper") }, variant format!("facing={}", facing.as_string()), @@ -2738,13 +3449,15 @@ define_blocks! { StairShape::OuterLeft, StairShape::OuterRight ], + waterlogged: bool = [true, false], }, - data stair_data(facing, half, shape), + data stair_data(facing, half, shape, waterlogged), + offset stair_offset(facing, half, shape, waterlogged), material material::NON_SOLID, model { ("minecraft", "quartz_stairs") }, variant format!("facing={},half={},shape={}", facing.as_string(), half.as_string(), shape.as_string()), collision stair_collision(facing, shape, half), - update_state (world, pos) => Block::QuartzStairs{facing: facing, half: half, shape: update_stair_shape(world, pos, facing)}, + update_state (world, pos) => Block::QuartzStairs{facing, half, shape: update_stair_shape(world, pos, facing), waterlogged}, } ActivatorRail { props { @@ -2759,6 +3472,7 @@ define_blocks! { powered: bool = [false, true], }, data Some(shape.data() | (if powered { 0x8 } else { 0x0 })), + offset Some(shape.data() + (if powered { 0 } else { 6 })), material material::NON_SOLID, model { ("minecraft", "activator_rail") }, variant format!("powered={},shape={}", powered, shape.as_string()), @@ -2777,6 +3491,7 @@ define_blocks! { triggered: bool = [false, true], }, data Some(facing.index() | (if triggered { 0x8 } else { 0x0 })), + offset Some(if triggered { 0 } else { 1 } + facing.offset() * 2), model { ("minecraft", "dropper") }, variant format!("facing={}", facing.as_string()), } @@ -2828,14 +3543,21 @@ define_blocks! { south: bool = [false, true], east: bool = [false, true], west: bool = [false, true], + waterlogged: bool = [true, false], }, - data if !north && !south && !east && !west { Some(color.data()) } else { None }, + data if !north && !south && !east && !west && !waterlogged { Some(color.data()) } else { None }, + offset Some(if west { 0 } else { 1<<0 } + + if waterlogged { 0 } else { 1<<1 } + + if south { 0 } else { 1<<2 } + + if north { 0 } else { 1<<3 } + + if east { 0 } else { 1<<4 } + + color.data() * (1<<5)), material material::TRANSPARENT, model { ("minecraft", format!("{}_stained_glass_pane", color.as_string()) ) }, collision pane_collision(north, south, east, west), update_state (world, pos) => { let (north, south, west, east) = can_connect_sides(world, pos, &can_connect_glasspane); - Block::StainedGlassPane{color: color, north: north, south: south, west: west, east: east} + Block::StainedGlassPane{color, north, south, west, east, waterlogged} }, multipart (key, val) => match key { "north" => north == (val == "true"), @@ -2857,6 +3579,7 @@ define_blocks! { data Some(variant.data() | (if decayable { 0x4 } else { 0x0 }) | (if check_decay { 0x8 } else { 0x0 })), + offset None, material material::LEAVES, model { ("minecraft", format!("{}_leaves", variant.as_string()) ) }, tint TintType::Foliage, @@ -2870,6 +3593,7 @@ define_blocks! { ], }, data Some(variant.data() | (axis.index() << 2)), + offset None, model { ("minecraft", format!("{}_log", variant.as_string()) ) }, variant format!("axis={}", axis.as_string()), } @@ -2889,13 +3613,15 @@ define_blocks! { StairShape::OuterLeft, StairShape::OuterRight ], + waterlogged: bool = [true, false], }, - data stair_data(facing, half, shape), + data stair_data(facing, half, shape, waterlogged), + offset stair_offset(facing, half, shape, waterlogged), material material::NON_SOLID, model { ("minecraft", "acacia_stairs") }, variant format!("facing={},half={},shape={}", facing.as_string(), half.as_string(), shape.as_string()), collision stair_collision(facing, shape, half), - update_state (world, pos) => Block::AcaciaStairs{facing: facing, half: half, shape: update_stair_shape(world, pos, facing)}, + update_state (world, pos) => Block::AcaciaStairs{facing, half, shape: update_stair_shape(world, pos, facing), waterlogged}, } DarkOakStairs { props { @@ -2913,13 +3639,15 @@ define_blocks! { StairShape::OuterLeft, StairShape::OuterRight ], + waterlogged: bool = [true, false], }, - data stair_data(facing, half, shape), + data stair_data(facing, half, shape, waterlogged), + offset stair_offset(facing, half, shape, waterlogged), material material::NON_SOLID, model { ("minecraft", "dark_oak_stairs") }, variant format!("facing={},half={},shape={}", facing.as_string(), half.as_string(), shape.as_string()), collision stair_collision(facing, shape, half), - update_state (world, pos) => Block::DarkOakStairs{facing: facing, half: half, shape: update_stair_shape(world, pos, facing)}, + update_state (world, pos) => Block::DarkOakStairs{facing, half, shape: update_stair_shape(world, pos, facing), waterlogged}, } Slime { props {}, @@ -2941,14 +3669,21 @@ define_blocks! { ], half: BlockHalf = [BlockHalf::Top, BlockHalf::Bottom], open: bool = [false, true], + waterlogged: bool = [true, false], + powered: bool = [true, false], }, - data Some(match facing { + data if waterlogged || powered { None } else { Some(match facing { Direction::North => 0, Direction::South => 1, Direction::West => 2, Direction::East => 3, _ => unreachable!(), - } | (if open { 0x4 } else { 0x0 }) | (if half == BlockHalf::Top { 0x8 } else { 0x0 })), + } | (if open { 0x4 } else { 0x0 }) | (if half == BlockHalf::Top { 0x8 } else { 0x0 }))}, + offset Some(if waterlogged { 0 } else { 1<<0 } + + if powered { 0 } else { 1<<1 } + + if open { 0 } else { 1<<2 } + + if half == BlockHalf::Top { 0 } else { 1<<3 } + + facing.horizontal_offset() * (1<<4)), material material::NON_SOLID, model { ("minecraft", "iron_trapdoor") }, variant format!("facing={},half={},open={}", facing.as_string(), half.as_string(), open), @@ -2965,6 +3700,66 @@ define_blocks! { data Some(variant.data()), model { ("minecraft", variant.as_string() ) }, } + PrismarineStairs { + props { + facing: Direction = [ + Direction::North, + Direction::South, + Direction::West, + Direction::East + ], + half: BlockHalf = [BlockHalf::Top, BlockHalf::Bottom], + shape: StairShape = [ + StairShape::Straight, + StairShape::InnerLeft, + StairShape::InnerRight, + StairShape::OuterLeft, + StairShape::OuterRight + ], + waterlogged: bool = [true, false], + variant: PrismarineVariant = [ + PrismarineVariant::Normal, + PrismarineVariant::Brick, + PrismarineVariant::Dark + ], + }, + data None::, + offset Some(stair_offset(facing, half, shape, waterlogged).unwrap() + (2 * 5 * 2 * 4) * variant.data()), + material material::NON_SOLID, + model { ("minecraft", match variant { + PrismarineVariant::Normal => "prismarine_stairs", + PrismarineVariant::Brick => "prismarine_brick_stairs", + PrismarineVariant::Dark => "dark_prismarine_stairs", + }) }, + variant format!("facing={},half={},shape={}", facing.as_string(), half.as_string(), shape.as_string()), + collision stair_collision(facing, shape, half), + update_state (world, pos) => Block::PrismarineStairs{facing, half, shape: update_stair_shape(world, pos, facing), waterlogged, variant}, + } + PrismarineSlab { + props { + type_: BlockHalf = [ + BlockHalf::Top, + BlockHalf::Bottom, + BlockHalf::Double + ], + waterlogged: bool = [true, false], + variant: PrismarineVariant = [ + PrismarineVariant::Normal, + PrismarineVariant::Brick, + PrismarineVariant::Dark + ], + }, + data None::, + offset Some(if waterlogged { 0 } else { 1 } + type_.offset() * 2 + variant.data() * (2 * 3)), + material material::NON_SOLID, + model { ("minecraft", match variant { + PrismarineVariant::Normal => "prismarine_slab", + PrismarineVariant::Brick => "prismarine_brick_slab", + PrismarineVariant::Dark => "dark_prismarine_slab", + }) }, + variant format!("type={}", type_.as_string()), + collision slab_collision(type_), + } SeaLantern { props {}, material Material { @@ -2978,6 +3773,7 @@ define_blocks! { axis: Axis = [Axis::X, Axis::Y, Axis::Z], }, data Some(match axis { Axis::X => 0x4, Axis::Y => 0x0, Axis::Z => 0x8, _ => unreachable!() }), + offset Some(match axis { Axis::X => 0, Axis::Y => 1, Axis::Z => 2, _ => unreachable!() }), model { ("minecraft", "hay_block") }, variant format!("axis={}", axis.as_string()), } @@ -3035,6 +3831,7 @@ define_blocks! { ], }, data Some(variant.data() | (if half == BlockHalf::Upper { 0x8 } else { 0x0 })), + offset Some(half.offset() + variant.offset() * 2), material material::NON_SOLID, model { ("minecraft", variant.as_string()) }, variant format!("half={}", half.as_string()), @@ -3042,7 +3839,7 @@ define_blocks! { collision vec![], update_state (world, pos) => { let (half, variant) = update_double_plant_state(world, pos, half, variant); - Block::DoublePlant{half: half, variant: variant} + Block::DoublePlant{half, variant} }, } StandingBanner { @@ -3065,8 +3862,27 @@ define_blocks! { Rotation::SouthEast, Rotation::SouthSouthEast ], + color: ColoredVariant = [ + ColoredVariant::White, + ColoredVariant::Orange, + ColoredVariant::Magenta, + ColoredVariant::LightBlue, + ColoredVariant::Yellow, + ColoredVariant::Lime, + ColoredVariant::Pink, + ColoredVariant::Gray, + ColoredVariant::Silver, + ColoredVariant::Cyan, + ColoredVariant::Purple, + ColoredVariant::Blue, + ColoredVariant::Brown, + ColoredVariant::Green, + ColoredVariant::Red, + ColoredVariant::Black + ], }, - data Some(rotation.data()), + data if color != ColoredVariant::White { None } else { Some(rotation.data()) }, + offset Some(rotation.data() + color.data() * 16), material material::NON_SOLID, model { ("minecraft", "standing_banner") }, variant format!("rotation={}", rotation.as_string()), @@ -3079,8 +3895,27 @@ define_blocks! { Direction::West, Direction::East ], + color: ColoredVariant = [ + ColoredVariant::White, + ColoredVariant::Orange, + ColoredVariant::Magenta, + ColoredVariant::LightBlue, + ColoredVariant::Yellow, + ColoredVariant::Lime, + ColoredVariant::Pink, + ColoredVariant::Gray, + ColoredVariant::Silver, + ColoredVariant::Cyan, + ColoredVariant::Purple, + ColoredVariant::Blue, + ColoredVariant::Brown, + ColoredVariant::Green, + ColoredVariant::Red, + ColoredVariant::Black + ], }, - data Some(facing.index()), + data if color != ColoredVariant::White { None } else { Some(facing.index()) }, + offset Some(facing.horizontal_offset() + color.data() * 4), material material::NON_SOLID, model { ("minecraft", "wall_banner") }, variant format!("facing={}", facing.as_string()), @@ -3090,6 +3925,7 @@ define_blocks! { power: u8 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], }, data Some(power as usize), + offset None, material material::NON_SOLID, model { ("minecraft", "daylight_detector_inverted") }, variant format!("power={}", power), @@ -3125,13 +3961,63 @@ define_blocks! { StairShape::OuterLeft, StairShape::OuterRight ], + waterlogged: bool = [true, false], }, - data stair_data(facing, half, shape), + data stair_data(facing, half, shape, waterlogged), + offset stair_offset(facing, half, shape, waterlogged), material material::NON_SOLID, model { ("minecraft", "red_sandstone_stairs") }, variant format!("facing={},half={},shape={}", facing.as_string(), half.as_string(), shape.as_string()), collision stair_collision(facing, shape, half), - update_state (world, pos) => Block::RedSandstoneStairs{facing: facing, half: half, shape: update_stair_shape(world, pos, facing)}, + update_state (world, pos) => Block::RedSandstoneStairs{facing, half, shape: update_stair_shape(world, pos, facing), waterlogged}, + } + WoodenSlabFlat { + props { + type_: BlockHalf = [ + BlockHalf::Top, + BlockHalf::Bottom, + BlockHalf::Double + ], + waterlogged: bool = [true, false], + variant: WoodSlabVariant = [ + WoodSlabVariant::Oak, + WoodSlabVariant::Spruce, + WoodSlabVariant::Birch, + WoodSlabVariant::Jungle, + WoodSlabVariant::Acacia, + WoodSlabVariant::DarkOak + ], + }, + data None::, + offset Some(if waterlogged { 0 } else { 1 } + type_.offset() * 2 + variant.data() * (2 * 3)), + material material::NON_SOLID, + model { ("minecraft", format!("{}_slab", variant.as_string()) ) }, + variant format!("type={}", type_.as_string()), + collision slab_collision(type_), + } + StoneSlabFlat { + props { + type_: BlockHalf = [BlockHalf::Top, BlockHalf::Bottom, BlockHalf::Double], + variant: StoneSlabVariant = [ + StoneSlabVariant::Stone, + StoneSlabVariant::Sandstone, + StoneSlabVariant::PetrifiedWood, + StoneSlabVariant::Cobblestone, + StoneSlabVariant::Brick, + StoneSlabVariant::StoneBrick, + StoneSlabVariant::NetherBrick, + StoneSlabVariant::Quartz, + StoneSlabVariant::RedSandstone, + StoneSlabVariant::Purpur + ], + waterlogged: bool = [true, false], + }, + data None::, + offset Some(if waterlogged { 0 } else { 1 } + type_.offset() * 2 + variant.offset() * (2 * 3)), + material material::NON_SOLID, + model { ("minecraft", format!("{}_slab", variant.as_string()) ) }, + variant format!("type={}", type_.as_string()), + collision slab_collision(type_), } DoubleStoneSlab2 { props { @@ -3141,6 +4027,7 @@ define_blocks! { ], }, data Some(variant.data() | (if seamless { 0x8 } else { 0x0 })), + offset None, material material::SOLID, model { ("minecraft", format!("{}_double_slab", variant.as_string()) ) }, variant if seamless { "all" } else { "normal" }, @@ -3151,11 +4038,31 @@ define_blocks! { variant: StoneSlabVariant = [StoneSlabVariant::RedSandstone], }, data Some(variant.data() | (if half == BlockHalf::Top { 0x8 } else { 0x0 })), + offset None, material material::NON_SOLID, model { ("minecraft", format!("{}_slab", variant.as_string()) ) }, variant format!("half={}", half.as_string()), collision slab_collision(half), } + SmoothStone { + props { + variant: StoneSlabVariant = [ + StoneSlabVariant::Stone, + StoneSlabVariant::Sandstone, + StoneSlabVariant::Quartz, + StoneSlabVariant::RedSandstone + ], + }, + data None::, + offset Some(match variant { + StoneSlabVariant::Stone => 0, + StoneSlabVariant::Sandstone => 1, + StoneSlabVariant::Quartz => 2, + StoneSlabVariant::RedSandstone => 3, + _ => unreachable!(), + }), + model { ("minecraft", format!("smooth_{}", variant.as_string()) ) }, + } SpruceFenceGate { props { facing: Direction = [ @@ -3169,6 +4076,7 @@ define_blocks! { powered: bool = [false, true], }, data fence_gate_data(facing, in_wall, open, powered), + offset fence_gate_offset(facing, in_wall, open, powered), material material::NON_SOLID, model { ("minecraft", "spruce_fence_gate") }, variant format!("facing={},in_wall={},open={}", facing.as_string(), in_wall, open), @@ -3193,6 +4101,7 @@ define_blocks! { powered: bool = [false, true], }, data fence_gate_data(facing, in_wall, open, powered), + offset fence_gate_offset(facing, in_wall, open, powered), material material::NON_SOLID, model { ("minecraft", "birch_fence_gate") }, variant format!("facing={},in_wall={},open={}", facing.as_string(), in_wall, open), @@ -3217,6 +4126,7 @@ define_blocks! { powered: bool = [false, true], }, data fence_gate_data(facing, in_wall, open, powered), + offset fence_gate_offset(facing, in_wall, open, powered), material material::NON_SOLID, model { ("minecraft", "jungle_fence_gate") }, variant format!("facing={},in_wall={},open={}", facing.as_string(), in_wall, open), @@ -3241,6 +4151,7 @@ define_blocks! { powered: bool = [false, true], }, data fence_gate_data(facing, in_wall, open, powered), + offset fence_gate_offset(facing, in_wall, open, powered), material material::NON_SOLID, model { ("minecraft", "dark_oak_fence_gate") }, variant format!("facing={},in_wall={},open={}", facing.as_string(), in_wall, open), @@ -3265,6 +4176,7 @@ define_blocks! { powered: bool = [false, true], }, data fence_gate_data(facing, in_wall, open, powered), + offset fence_gate_offset(facing, in_wall, open, powered), material material::NON_SOLID, model { ("minecraft", "acacia_fence_gate") }, variant format!("facing={},in_wall={},open={}", facing.as_string(), in_wall, open), @@ -3282,14 +4194,20 @@ define_blocks! { south: bool = [false, true], west: bool = [false, true], east: bool = [false, true], + waterlogged: bool = [true, false], }, - data if !north && !south && !west && !east { Some(0) } else { None }, + data if !north && !south && !west && !east && !waterlogged { Some(0) } else { None }, + offset Some(if west { 0 } else { 1<<0 } + + if waterlogged { 0 } else { 1<<1 } + + if south { 0 } else { 1<<2 } + + if north { 0 } else { 1<<3 } + + if east { 0 } else { 1<<4 }), material material::NON_SOLID, model { ("minecraft", "spruce_fence") }, collision fence_collision(north, south, west, east), update_state (world, pos) => { let (north, south, west, east) = can_connect_sides(world, pos, &can_connect_fence); - Block::SpruceFence{north: north, south: south, west: west, east: east} + Block::SpruceFence{north, south, west, east, waterlogged} }, multipart (key, val) => match key { "north" => north == (val == "true"), @@ -3305,14 +4223,20 @@ define_blocks! { south: bool = [false, true], west: bool = [false, true], east: bool = [false, true], + waterlogged: bool = [true, false], }, - data if !north && !south && !west && !east { Some(0) } else { None }, + data if !north && !south && !west && !east && !waterlogged { Some(0) } else { None }, + offset Some(if west { 0 } else { 1<<0 } + + if waterlogged { 0 } else { 1<<1 } + + if south { 0 } else { 1<<2 } + + if north { 0 } else { 1<<3 } + + if east { 0 } else { 1<<4 }), material material::NON_SOLID, model { ("minecraft", "birch_fence") }, collision fence_collision(north, south, west, east), update_state (world, pos) => { let (north, south, west, east) = can_connect_sides(world, pos, &can_connect_fence); - Block::BirchFence{north: north, south: south, west: west, east: east} + Block::BirchFence{north, south, west, east, waterlogged} }, multipart (key, val) => match key { "north" => north == (val == "true"), @@ -3328,14 +4252,20 @@ define_blocks! { south: bool = [false, true], west: bool = [false, true], east: bool = [false, true], + waterlogged: bool = [true, false], }, - data if !north && !south && !west && !east { Some(0) } else { None }, + data if !north && !south && !west && !east && !waterlogged { Some(0) } else { None }, + offset Some(if west { 0 } else { 1<<0 } + + if waterlogged { 0 } else { 1<<1 } + + if south { 0 } else { 1<<2 } + + if north { 0 } else { 1<<3 } + + if east { 0 } else { 1<<4 }), material material::NON_SOLID, model { ("minecraft", "jungle_fence") }, collision fence_collision(north, south, west, east), update_state (world, pos) => { let (north, south, west, east) = can_connect_sides(world, pos, &can_connect_fence); - Block::JungleFence{north: north, south: south, west: west, east: east} + Block::JungleFence{north, south, west, east, waterlogged} }, multipart (key, val) => match key { "north" => north == (val == "true"), @@ -3351,14 +4281,20 @@ define_blocks! { south: bool = [false, true], west: bool = [false, true], east: bool = [false, true], + waterlogged: bool = [true, false], }, - data if !north && !south && !west && !east { Some(0) } else { None }, + data if !north && !south && !west && !east && !waterlogged { Some(0) } else { None }, + offset Some(if west { 0 } else { 1<<0 } + + if waterlogged { 0 } else { 1<<1 } + + if south { 0 } else { 1<<2 } + + if north { 0 } else { 1<<3 } + + if east { 0 } else { 1<<4 }), material material::NON_SOLID, model { ("minecraft", "dark_oak_fence") }, collision fence_collision(north, south, west, east), update_state (world, pos) => { let (north, south, west, east) = can_connect_sides(world, pos, &can_connect_fence); - Block::DarkOakFence{north: north, south: south, east: east, west: west} + Block::DarkOakFence{north, south, west, east, waterlogged} }, multipart (key, val) => match key { "north" => north == (val == "true"), @@ -3374,14 +4310,20 @@ define_blocks! { south: bool = [false, true], west: bool = [false, true], east: bool = [false, true], + waterlogged: bool = [true, false], }, - data if !north && !south && !east && !west { Some(0) } else { None }, + data if !north && !south && !west && !east && !waterlogged { Some(0) } else { None }, + offset Some(if west { 0 } else { 1<<0 } + + if waterlogged { 0 } else { 1<<1 } + + if south { 0 } else { 1<<2 } + + if north { 0 } else { 1<<3 } + + if east { 0 } else { 1<<4 }), material material::NON_SOLID, model { ("minecraft", "acacia_fence") }, collision fence_collision(north, south, west, east), update_state (world, pos) => { let (north, south, west, east) = can_connect_sides(world, pos, &can_connect_fence); - Block::AcaciaFence{north: north, south: south, east: east, west: west} + Block::AcaciaFence{north, south, west, east, waterlogged} }, multipart (key, val) => match key { "north" => north == (val == "true"), @@ -3405,6 +4347,7 @@ define_blocks! { powered: bool = [false, true], }, data door_data(facing, half, hinge, open, powered), + offset door_offset(facing, half, hinge, open, powered), material material::NON_SOLID, model { ("minecraft", "spruce_door") }, variant format!("facing={},half={},hinge={},open={}", facing.as_string(), half.as_string(), hinge.as_string(), open), @@ -3428,6 +4371,7 @@ define_blocks! { powered: bool = [false, true], }, data door_data(facing, half, hinge, open, powered), + offset door_offset(facing, half, hinge, open, powered), material material::NON_SOLID, model { ("minecraft", "birch_door") }, variant format!("facing={},half={},hinge={},open={}", facing.as_string(), half.as_string(), hinge.as_string(), open), @@ -3451,6 +4395,7 @@ define_blocks! { powered: bool = [false, true], }, data door_data(facing, half, hinge, open, powered), + offset door_offset(facing, half, hinge, open, powered), material material::NON_SOLID, model { ("minecraft", "jungle_door") }, variant format!("facing={},half={},hinge={},open={}", facing.as_string(), half.as_string(), hinge.as_string(), open), @@ -3474,6 +4419,7 @@ define_blocks! { powered: bool = [false, true], }, data door_data(facing, half, hinge, open, powered), + offset door_offset(facing, half, hinge, open, powered), material material::NON_SOLID, model { ("minecraft", "acacia_door") }, variant format!("facing={},half={},hinge={},open={}", facing.as_string(), half.as_string(), hinge.as_string(), open), @@ -3497,6 +4443,7 @@ define_blocks! { powered: bool = [false, true], }, data door_data(facing, half, hinge, open, powered), + offset door_offset(facing, half, hinge, open, powered), material material::NON_SOLID, model { ("minecraft", "dark_oak_door") }, variant format!("facing={},half={},hinge={},open={}", facing.as_string(), half.as_string(), hinge.as_string(), open), @@ -3518,6 +4465,7 @@ define_blocks! { ], }, data Some(facing.index()), + offset Some(facing.offset()), material Material { emitted_light: 14, ..material::NON_SOLID @@ -3552,6 +4500,12 @@ define_blocks! { east: bool = [false, true], }, data if !up && !down && !north && !south && !west && !east { Some(0) } else { None }, + offset Some(if west { 0 } else { 1<<0 } + + if up { 0 } else { 1<<1 } + + if south { 0 } else { 1<<2 } + + if north { 0 } else { 1<<3 } + + if east { 0 } else { 1<<4 } + + if down { 0 } else { 1<<5 }), material material::NON_SOLID, model { ("minecraft", "chorus_plant") }, collision { @@ -3640,6 +4594,7 @@ define_blocks! { axis: Axis = [Axis::X, Axis::Y, Axis::Z], }, data Some(match axis { Axis::X => 0x4, Axis::Y => 0x0, Axis::Z => 0x8, _ => unreachable!() }), + offset Some(match axis { Axis::X => 0, Axis::Y => 1, Axis::Z => 2, _ => unreachable!() }), model { ("minecraft", "purpur_pillar") }, variant format!("axis={}", axis.as_string()), } @@ -3659,18 +4614,21 @@ define_blocks! { StairShape::OuterLeft, StairShape::OuterRight ], + waterlogged: bool = [true, false], }, - data stair_data(facing, half, shape), + data stair_data(facing, half, shape, waterlogged), + offset stair_offset(facing, half, shape, waterlogged), material material::NON_SOLID, model { ("minecraft", "purpur_stairs") }, variant format!("facing={},half={},shape={}", facing.as_string(), half.as_string(), shape.as_string()), collision stair_collision(facing, shape, half), - update_state (world, pos) => Block::PurpurStairs{facing: facing, half: half, shape: update_stair_shape(world, pos, facing)}, + update_state (world, pos) => Block::PurpurStairs{facing, half, shape: update_stair_shape(world, pos, facing), waterlogged}, } PurpurDoubleSlab { props { variant: StoneSlabVariant = [StoneSlabVariant::Purpur], }, + offset None, model { ("minecraft", format!("{}_double_slab", variant.as_string()) ) }, } PurpurSlab { @@ -3679,6 +4637,7 @@ define_blocks! { variant: StoneSlabVariant = [StoneSlabVariant::Purpur], }, data if half == BlockHalf::Top { Some(0x8) } else { Some(0) }, + offset None, material material::NON_SOLID, model { ("minecraft", format!("{}_slab", variant.as_string()) ) }, variant format!("half={},variant=default", half.as_string()), @@ -3726,6 +4685,7 @@ define_blocks! { ], }, data Some(facing.index() | (if conditional { 0x8 } else { 0x0 })), + offset Some(facing.offset() + (if conditional { 0 } else { 6 })), model { ("minecraft", "repeating_command_block") }, variant format!("conditional={},facing={}", conditional, facing.as_string()), } @@ -3742,11 +4702,16 @@ define_blocks! { ], }, data Some(facing.index() | (if conditional { 0x8 } else { 0x0 })), + offset Some(facing.offset() + (if conditional { 0 } else { 6 })), model { ("minecraft", "chain_command_block") }, variant format!("conditional={},facing={}", conditional, facing.as_string()), } FrostedIce { - props {}, + props { + age: u8 = [ 0, 1, 2, 3 ], + }, + data if age == 0 { Some(0) } else { None }, + offset Some(age as usize), model { ("minecraft", "frosted_ice") }, } MagmaBlock { @@ -3766,6 +4731,7 @@ define_blocks! { axis: Axis = [Axis::Y, Axis::Z, Axis::X], }, data Some(axis.index() << 2), + offset Some(match axis { Axis::X => 0, Axis::Y => 1, Axis::Z => 2, _ => unreachable!() }), model { ("minecraft", "bone_block") }, variant format!("axis={}", axis.as_string()), } @@ -3792,12 +4758,28 @@ define_blocks! { powered: bool = [false, true], }, data Some(facing.index() | (if powered { 0x8 } else { 0x0 })), + offset Some(if powered { 0 } else { 1 } + facing.offset() * 2), model { ("minecraft", "observer") }, variant format!("facing={},powered={}", facing.as_string(), powered), } // TODO: Shulker box textures (1.11+), since there is no model, we use wool for now // The textures should be built from textures/blocks/shulker_top_.png // and textures/entity/shulker/shulker_.png + ShulkerBox { + props { + facing: Direction = [ + Direction::Up, + Direction::Down, + Direction::North, + Direction::South, + Direction::West, + Direction::East + ], + }, + data None::, + offset Some(facing.offset()), + model { ("minecraft", "sponge") }, + } WhiteShulkerBox { props { facing: Direction = [ @@ -3810,6 +4792,7 @@ define_blocks! { ], }, data Some(facing.index()), + offset Some(facing.offset()), model { ("minecraft", "white_wool") }, } OrangeShulkerBox { @@ -3824,6 +4807,7 @@ define_blocks! { ], }, data Some(facing.index()), + offset Some(facing.offset()), model { ("minecraft", "orange_wool") }, } MagentaShulkerBox { @@ -3838,6 +4822,7 @@ define_blocks! { ], }, data Some(facing.index()), + offset Some(facing.offset()), model { ("minecraft", "magenta_wool") }, } LightBlueShulkerBox { @@ -3852,6 +4837,7 @@ define_blocks! { ], }, data Some(facing.index()), + offset Some(facing.offset()), model { ("minecraft", "light_blue_wool") }, } YellowShulkerBox { @@ -3866,6 +4852,7 @@ define_blocks! { ], }, data Some(facing.index()), + offset Some(facing.offset()), model { ("minecraft", "yellow_wool") }, } LimeShulkerBox { @@ -3880,6 +4867,7 @@ define_blocks! { ], }, data Some(facing.index()), + offset Some(facing.offset()), model { ("minecraft", "lime_wool") }, } PinkShulkerBox { @@ -3894,6 +4882,7 @@ define_blocks! { ], }, data Some(facing.index()), + offset Some(facing.offset()), model { ("minecraft", "pink_wool") }, } GrayShulkerBox { @@ -3908,6 +4897,7 @@ define_blocks! { ], }, data Some(facing.index()), + offset Some(facing.offset()), model { ("minecraft", "gray_wool") }, } LightGrayShulkerBox { @@ -3922,6 +4912,7 @@ define_blocks! { ], }, data Some(facing.index()), + offset Some(facing.offset()), model { ("minecraft", "light_gray_wool") }, } CyanShulkerBox { @@ -3936,6 +4927,7 @@ define_blocks! { ], }, data Some(facing.index()), + offset Some(facing.offset()), model { ("minecraft", "cyan_wool") }, } PurpleShulkerBox { @@ -3950,6 +4942,7 @@ define_blocks! { ], }, data Some(facing.index()), + offset Some(facing.offset()), model { ("minecraft", "purple_wool") }, } BlueShulkerBox { @@ -3964,6 +4957,7 @@ define_blocks! { ], }, data Some(facing.index()), + offset Some(facing.offset()), model { ("minecraft", "blue_wool") }, } BrownShulkerBox { @@ -3978,6 +4972,7 @@ define_blocks! { ], }, data Some(facing.index()), + offset Some(facing.offset()), model { ("minecraft", "brown_wool") }, } GreenShulkerBox { @@ -3992,6 +4987,7 @@ define_blocks! { ], }, data Some(facing.index()), + offset Some(facing.offset()), model { ("minecraft", "green_wool") }, } RedShulkerBox { @@ -4006,6 +5002,7 @@ define_blocks! { ], }, data Some(facing.index()), + offset Some(facing.offset()), model { ("minecraft", "red_wool") }, } BlackShulkerBox { @@ -4020,6 +5017,7 @@ define_blocks! { ], }, data Some(facing.index()), + offset Some(facing.offset()), model { ("minecraft", "black_wool") }, } WhiteGlazedTerracotta { @@ -4032,6 +5030,7 @@ define_blocks! { ], }, data Some(facing.horizontal_index()), + offset Some(facing.horizontal_offset()), model { ("minecraft", "white_glazed_terracotta") }, variant format!("facing={}", facing.as_string()), } @@ -4045,6 +5044,7 @@ define_blocks! { ], }, data Some(facing.horizontal_index()), + offset Some(facing.horizontal_offset()), model { ("minecraft", "orange_glazed_terracotta") }, variant format!("facing={}", facing.as_string()), } @@ -4058,6 +5058,7 @@ define_blocks! { ], }, data Some(facing.horizontal_index()), + offset Some(facing.horizontal_offset()), model { ("minecraft", "magenta_glazed_terracotta") }, variant format!("facing={}", facing.as_string()), } @@ -4071,6 +5072,7 @@ define_blocks! { ], }, data Some(facing.horizontal_index()), + offset Some(facing.horizontal_offset()), model { ("minecraft", "light_blue_glazed_terracotta") }, variant format!("facing={}", facing.as_string()), } @@ -4084,6 +5086,7 @@ define_blocks! { ], }, data Some(facing.horizontal_index()), + offset Some(facing.horizontal_offset()), model { ("minecraft", "yellow_glazed_terracotta") }, variant format!("facing={}", facing.as_string()), } @@ -4097,6 +5100,7 @@ define_blocks! { ], }, data Some(facing.horizontal_index()), + offset Some(facing.horizontal_offset()), model { ("minecraft", "lime_glazed_terracotta") }, variant format!("facing={}", facing.as_string()), } @@ -4110,6 +5114,7 @@ define_blocks! { ], }, data Some(facing.horizontal_index()), + offset Some(facing.horizontal_offset()), model { ("minecraft", "pink_glazed_terracotta") }, variant format!("facing={}", facing.as_string()), } @@ -4123,6 +5128,7 @@ define_blocks! { ], }, data Some(facing.horizontal_index()), + offset Some(facing.horizontal_offset()), model { ("minecraft", "gray_glazed_terracotta") }, variant format!("facing={}", facing.as_string()), } @@ -4136,6 +5142,7 @@ define_blocks! { ], }, data Some(facing.horizontal_index()), + offset Some(facing.horizontal_offset()), model { ("minecraft", "silver_glazed_terracotta") }, variant format!("facing={}", facing.as_string()), } @@ -4149,6 +5156,7 @@ define_blocks! { ], }, data Some(facing.horizontal_index()), + offset Some(facing.horizontal_offset()), model { ("minecraft", "cyan_glazed_terracotta") }, variant format!("facing={}", facing.as_string()), } @@ -4162,6 +5170,7 @@ define_blocks! { ], }, data Some(facing.horizontal_index()), + offset Some(facing.horizontal_offset()), model { ("minecraft", "purple_glazed_terracotta") }, variant format!("facing={}", facing.as_string()), } @@ -4175,6 +5184,7 @@ define_blocks! { ], }, data Some(facing.horizontal_index()), + offset Some(facing.horizontal_offset()), model { ("minecraft", "blue_glazed_terracotta") }, variant format!("facing={}", facing.as_string()), } @@ -4188,6 +5198,7 @@ define_blocks! { ], }, data Some(facing.horizontal_index()), + offset Some(facing.horizontal_offset()), model { ("minecraft", "brown_glazed_terracotta") }, variant format!("facing={}", facing.as_string()), } @@ -4201,6 +5212,7 @@ define_blocks! { ], }, data Some(facing.horizontal_index()), + offset Some(facing.horizontal_offset()), model { ("minecraft", "green_glazed_terracotta") }, variant format!("facing={}", facing.as_string()), } @@ -4214,6 +5226,7 @@ define_blocks! { ], }, data Some(facing.horizontal_index()), + offset Some(facing.horizontal_offset()), model { ("minecraft", "red_glazed_terracotta") }, variant format!("facing={}", facing.as_string()), } @@ -4227,6 +5240,7 @@ define_blocks! { ], }, data Some(facing.horizontal_index()), + offset Some(facing.horizontal_offset()), model { ("minecraft", "black_glazed_terracotta") }, variant format!("facing={}", facing.as_string()), } @@ -4278,14 +5292,189 @@ define_blocks! { data Some(color.data()), model { ("minecraft", format!("{}_concrete_powder", color.as_string()) ) }, } - Missing253 { + Kelp { + props { + age: u8 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25], + }, + data None::, + offset Some(age as usize), + model { ("minecraft", "kelp") }, + } + KelpPlant { props {}, data None::, + offset Some(0), + model { ("minecraft", "kelp_plant") }, + } + DriedKelpBlock { + props {}, + data None::, + offset Some(0), + model { ("minecraft", "dried_kelp_block") }, + } + TurtleEgg { + props { + age: u8 = [1, 2, 3, 4], + hatch: u8 = [0, 1, 2], + }, + data None::, + offset Some((hatch as usize) + ((age - 1) as usize) * 3), + model { ("minecraft", "turtle_egg") }, + } + CoralBlock { + props { + variant: CoralVariant = [ + CoralVariant::DeadTube, + CoralVariant::DeadBrain, + CoralVariant::DeadBubble, + CoralVariant::DeadFire, + CoralVariant::DeadHorn, + CoralVariant::Tube, + CoralVariant::Brain, + CoralVariant::Bubble, + CoralVariant::Fire, + CoralVariant::Horn + ], + }, + data None::, + offset Some(variant.offset()), + model { ("minecraft", format!("{}_block", variant.as_string())) }, + } + Coral { + props { + waterlogged: bool = [true, false], + variant: CoralVariant = [ + CoralVariant::DeadTube, + CoralVariant::DeadBrain, + CoralVariant::DeadBubble, + CoralVariant::DeadFire, + CoralVariant::DeadHorn, + CoralVariant::Tube, + CoralVariant::Brain, + CoralVariant::Bubble, + CoralVariant::Fire, + CoralVariant::Horn + ], + }, + data None::, + offset Some(if waterlogged { 0 } else { 1 } + variant.offset() * 2), + model { ("minecraft", variant.as_string()) }, + } + CoralWallFan { + props { + facing: Direction = [ + Direction::North, + Direction::South, + Direction::West, + Direction::East + ], + waterlogged: bool = [true, false], + variant: CoralVariant = [ + CoralVariant::DeadTube, + CoralVariant::DeadBrain, + CoralVariant::DeadBubble, + CoralVariant::DeadFire, + CoralVariant::DeadHorn, + CoralVariant::Tube, + CoralVariant::Brain, + CoralVariant::Bubble, + CoralVariant::Fire, + CoralVariant::Horn + ], + }, + data None::, + offset Some(if waterlogged { 0 } else { 1 } + + facing.horizontal_offset() * 2 + + variant.offset() * (2 * 4)), + model { ("minecraft", format!("{}_wall_fan", variant.as_string())) }, + } + CoralFan { + props { + waterlogged: bool = [true, false], + variant: CoralVariant = [ + CoralVariant::DeadTube, + CoralVariant::DeadBrain, + CoralVariant::DeadBubble, + CoralVariant::DeadFire, + CoralVariant::DeadHorn, + CoralVariant::Tube, + CoralVariant::Brain, + CoralVariant::Bubble, + CoralVariant::Fire, + CoralVariant::Horn + ], + }, + data None::, + offset Some(if waterlogged { 0 } else { 1 } + + variant.offset() * 2), + model { ("minecraft", format!("{}_fan", variant.as_string())) }, + } + SeaPickle { + props { + age: u8 = [1, 2, 3, 4], + waterlogged: bool = [true, false], + }, + data None::, + offset Some(if waterlogged { 0 } else { 1 } + + ((age - 1) as usize) * 2), + model { ("minecraft", "sea_pickle") }, + variant format!("age={}", age), + } + BlueIce { + props {}, + data None::, + offset Some(0), + model { ("minecraft", "blue_ice") }, + } + Conduit { + props { + waterlogged: bool = [true, false], + }, + data None::, + offset Some(if waterlogged { 0 } else { 1 }), + material material::NON_SOLID, + model { ("minecraft", "conduit") }, + } + VoidAir { + props {}, + data None::, + offset Some(0), + material material::Material { + collidable: false, + .. material::INVISIBLE + }, + model { ("minecraft", "air") }, + collision vec![], + } + CaveAir { + props {}, + data None::, + offset Some(0), + material material::Material { + collidable: false, + .. material::INVISIBLE + }, + model { ("minecraft", "air") }, + collision vec![], + } + BubbleColumn { + props { + drag: bool = [true, false], + }, + data None::, + offset Some(if drag { 0 } else { 1 }), + model { ("minecraft", "bubble_column") }, + } + Missing253 { + props {}, + data Some(0), + offset None, model { ("steven", "missing_block") }, } Missing254 { props {}, - data None::, + data Some(0), + offset None, model { ("steven", "missing_block") }, } StructureBlock { @@ -4427,6 +5616,13 @@ fn fence_gate_data(facing: Direction, in_wall: bool, open: bool, powered: bool) Some(facing.horizontal_index() | (if open { 0x4 } else { 0x0 })) } +fn fence_gate_offset(facing: Direction, in_wall: bool, open: bool, powered: bool) -> Option { + Some(if powered { 0 } else { 1<<0 } + + if open { 0 } else { 1<<1 } + + if in_wall { 0 } else { 1<<2 } + + facing.horizontal_offset() * (1<<3)) +} + fn fence_gate_collision(facing: Direction, in_wall: bool, open: bool) -> Vec> { if open { return vec![]; } @@ -4483,6 +5679,15 @@ fn door_data(facing: Direction, half: DoorHalf, hinge: Side, open: bool, powered } } +fn door_offset(facing: Direction, half: DoorHalf, hinge: Side, open: bool, powered: bool) -> Option { + Some(if powered { 0 } else { 1<<0 } + + if open { 0 } else { 1<<1 } + + if hinge == Side::Left { 0 } else { 1<<2 } + + if half == DoorHalf::Upper { 0 } else { 1<<3 } + + facing.horizontal_offset() * (1<<4)) +} + + fn update_door_state(world: &W, pos: Position, ohalf: DoorHalf, ofacing: Direction, ohinge: Side, oopen: bool, opowered: bool) -> (Direction, Side, bool, bool) { let oy = if ohalf == DoorHalf::Upper { -1 } else { 1 }; @@ -4724,12 +5929,20 @@ fn update_stair_shape(world: &W, pos: Position, facing: Directio StairShape::Straight } -fn stair_data(facing: Direction, half: BlockHalf, shape: StairShape) -> Option { +fn stair_data(facing: Direction, half: BlockHalf, shape: StairShape, waterlogged: bool) -> Option { if shape != StairShape::Straight { return None; } + if waterlogged { return None; } Some((5 - facing.index()) | (if half == BlockHalf::Top { 0x4 } else { 0x0 })) } +fn stair_offset(facing: Direction, half: BlockHalf, shape: StairShape, waterlogged: bool) -> Option { + Some(if waterlogged { 0 } else { 1 } + + shape.offset() * 2 + + if half == BlockHalf::Top { 0 } else { 2 * 5 } + + facing.horizontal_offset() * 2 * 5 * 2) +} + #[allow(clippy::many_single_char_names)] fn stair_collision(facing: Direction, shape: StairShape, half: BlockHalf) -> Vec> { use std::f64::consts::PI; @@ -4806,6 +6019,7 @@ fn slab_collision(half: BlockHalf) -> Vec> { let (min_x, min_y, min_z, max_x, max_y, max_z) = match half { BlockHalf::Top => (0.0, 0.5, 0.0, 1.0, 1.0, 1.0), BlockHalf::Bottom => (0.0, 0.0, 0.0, 1.0, 0.5, 1.0), + BlockHalf::Double => (0.0, 0.0, 0.0, 1.0, 1.0, 1.0), _ => unreachable!(), }; @@ -4916,6 +6130,53 @@ impl SandstoneVariant { } } +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum NoteBlockInstrument { + Harp, + BaseDrum, + Snare, + Hat, + Bass, + Flute, + Bell, + Guitar, + Chime, + Xylophone, +} + +impl NoteBlockInstrument { + pub fn as_string(self) -> &'static str { + match self { + NoteBlockInstrument::Harp => "harp", + NoteBlockInstrument::BaseDrum => "basedrum", + NoteBlockInstrument::Snare => "snare", + NoteBlockInstrument::Hat => "hat", + NoteBlockInstrument::Bass => "bass", + NoteBlockInstrument::Flute => "flute", + NoteBlockInstrument::Bell => "bell", + NoteBlockInstrument::Guitar => "guitar", + NoteBlockInstrument::Chime => "chime", + NoteBlockInstrument::Xylophone => "xylophone", + } + } + + fn offset(self) -> usize { + match self { + NoteBlockInstrument::Harp => 0, + NoteBlockInstrument::BaseDrum => 1, + NoteBlockInstrument::Snare => 2, + NoteBlockInstrument::Hat => 3, + NoteBlockInstrument::Bass => 4, + NoteBlockInstrument::Flute => 5, + NoteBlockInstrument::Bell => 6, + NoteBlockInstrument::Guitar => 7, + NoteBlockInstrument::Chime => 8, + NoteBlockInstrument::Xylophone => 9, + } + } +} + + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum RedSandstoneVariant { Normal, @@ -4996,59 +6257,61 @@ impl PrismarineVariant { } } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum MushroomVariant { - East, - North, - NorthEast, - NorthWest, - South, - SouthEast, - SouthWest, - West, - Center, - Stem, - AllInside, - AllOutside, - AllStem, +fn mushroom_block_data(is_stem: bool, west: bool, up: bool, south: bool, north: bool, east: bool, down: bool) -> Option { + Some(match + (is_stem, west, up, south, north, east, down) { + (false, false, false, false, false, false, false) => 0, + (false, true, false, false, true, false, false) => 1, + (false, false, false, false, true, false, false) => 2, + (false, false, false, false, true, true, false) => 3, + (false, true, false, false, false, false, false) => 4, + (false, false, true, false, false, false, false) => 5, + (false, false, false, false, false, true, false) => 6, + (false, true, false, true, false, false, false) => 7, + (false, false, false, true, false, false, false) => 8, + (false, false, false, true, false, true, false) => 9, + (false, true, false, true, true, true, false) => 10, + (false, true, true, true, true, true, true) => 14, + (true, false, false, false, false, false, false) => 15, + _ => return None, + }) } -impl MushroomVariant { - pub fn as_string(self) -> &'static str { - match self { - MushroomVariant::East => "east", - MushroomVariant::North => "north", - MushroomVariant::NorthEast => "north_east", - MushroomVariant::NorthWest => "north_west", - MushroomVariant::South => "south", - MushroomVariant::SouthEast => "south_east", - MushroomVariant::SouthWest => "south_west", - MushroomVariant::West => "west", - MushroomVariant::Center => "center", - MushroomVariant::Stem => "stem", - MushroomVariant::AllInside => "all_inside", - MushroomVariant::AllOutside => "all_outside", - MushroomVariant::AllStem => "all_stem", - } +fn mushroom_block_offset(is_stem: bool, west: bool, up: bool, south: bool, north: bool, east: bool, down: bool) -> Option { + if is_stem { + None + } else { + Some(if west { 0 } else { 1<<0 } + + if up { 0 } else { 1<<1 } + + if south { 0 } else { 1<<2 } + + if north { 0 } else { 1<<3 } + + if east { 0 } else { 1<<4 } + + if down { 0 } else { 1<<5 }) } +} - fn data(self) -> usize { - match self { - MushroomVariant::AllInside => 0, - MushroomVariant::NorthWest => 1, - MushroomVariant::North => 2, - MushroomVariant::NorthEast => 3, - MushroomVariant::West => 4, - MushroomVariant::Center => 5, - MushroomVariant::East => 6, - MushroomVariant::SouthWest => 7, - MushroomVariant::South => 8, - MushroomVariant::SouthEast => 9, - MushroomVariant::Stem => 10, - MushroomVariant::AllOutside => 14, - MushroomVariant::AllStem => 15, + +fn mushroom_block_variant(is_stem: bool, west: bool, up: bool, south: bool, north: bool, east: bool, down: bool) -> String { + (if is_stem { + "all_stem" + } else { + match + (west, up, south, north, east, down) { + (false, false, false, false, false, false) => "all_inside", + (true, false, false, true, false, false) => "north_west", + (false, false, false, true, false, false) => "north", + (false, false, false, true, true, false) => "north_east", + (true, false, false, false, false, false) => "west", + (false, true, false, false, false, false) => "center", + (false, false, false, false, true, false) => "east", + (true, false, true, false, false, false) => "south_west", + (false, false, true, false, false, false) => "south", + (false, false, true, false, true, false) => "south_east", + (true, false, true, true, true, false) => "stem", + (true, true, true, true, true, true) => "all_outside", + _ => "all_stem", } - } + }).to_string() } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] @@ -5311,46 +6574,6 @@ impl ComparatorMode { } } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum LeverDirection { - North, - South, - East, - West, - UpX, - DownX, - UpZ, - DownZ, -} - -impl LeverDirection { - pub fn as_string(self) -> &'static str { - match self { - LeverDirection::North => "north", - LeverDirection::South => "south", - LeverDirection::East => "east", - LeverDirection::West => "west", - LeverDirection::UpX => "up_x", - LeverDirection::DownX => "down_x", - LeverDirection::UpZ => "up_z", - LeverDirection::DownZ => "down_z", - } - } - - pub fn data(self) -> usize { - match self { - LeverDirection::DownX => 0, - LeverDirection::East => 1, - LeverDirection::West => 2, - LeverDirection::South => 3, - LeverDirection::North => 4, - LeverDirection::UpZ => 5, - LeverDirection::UpX => 6, - LeverDirection::DownZ => 7, - } - } -} - #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum RedstoneSide { None, @@ -5366,6 +6589,14 @@ impl RedstoneSide { RedstoneSide::Up => "up", } } + + pub fn offset(self) -> usize { + match self { + RedstoneSide::Up => 0, + RedstoneSide::Side => 1, + RedstoneSide::None => 2, + } + } } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] @@ -5387,7 +6618,7 @@ impl PistonType { pub enum StoneSlabVariant { Stone, Sandstone, - Wood, + PetrifiedWood, Cobblestone, Brick, StoneBrick, @@ -5402,7 +6633,7 @@ impl StoneSlabVariant { match self { StoneSlabVariant::Stone => "stone", StoneSlabVariant::Sandstone => "sandstone", - StoneSlabVariant::Wood => "wood_old", + StoneSlabVariant::PetrifiedWood => "wood_old", StoneSlabVariant::Cobblestone => "cobblestone", StoneSlabVariant::Brick => "brick", StoneSlabVariant::StoneBrick => "stone_brick", @@ -5419,7 +6650,7 @@ impl StoneSlabVariant { StoneSlabVariant::RedSandstone | StoneSlabVariant::Purpur => 0, StoneSlabVariant::Sandstone => 1, - StoneSlabVariant::Wood => 2, + StoneSlabVariant::PetrifiedWood => 2, StoneSlabVariant::Cobblestone => 3, StoneSlabVariant::Brick => 4, StoneSlabVariant::StoneBrick => 5, @@ -5427,6 +6658,21 @@ impl StoneSlabVariant { StoneSlabVariant::Quartz => 7, } } + + fn offset(self) -> usize { + match self { + StoneSlabVariant::Stone => 0, + StoneSlabVariant::Sandstone => 1, + StoneSlabVariant::PetrifiedWood => 2, + StoneSlabVariant::Cobblestone => 3, + StoneSlabVariant::Brick => 4, + StoneSlabVariant::StoneBrick => 5, + StoneSlabVariant::NetherBrick => 6, + StoneSlabVariant::Quartz => 7, + StoneSlabVariant::RedSandstone => 8, + StoneSlabVariant::Purpur => 9, + } + } } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] @@ -5469,6 +6715,7 @@ pub enum BlockHalf { Bottom, Upper, Lower, + Double, } impl BlockHalf { @@ -5478,6 +6725,15 @@ impl BlockHalf { BlockHalf::Bottom => "bottom", BlockHalf::Upper => "upper", BlockHalf::Lower => "lower", + BlockHalf::Double => "double", + } + } + + pub fn offset(self) -> usize { + match self { + BlockHalf::Top | BlockHalf::Upper => 0, + BlockHalf::Bottom | BlockHalf::Lower => 1, + BlockHalf::Double => 2, } } } @@ -5587,6 +6843,102 @@ impl StairShape { StairShape::OuterRight => "outer_right", } } + + pub fn offset(self) -> usize { + match self { + StairShape::Straight => 0, + StairShape::InnerLeft => 1, + StairShape::InnerRight => 2, + StairShape::OuterLeft => 3, + StairShape::OuterRight => 4, + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum AttachedFace { + Floor, + Wall, + Ceiling, +} + +impl AttachedFace { + pub fn as_string(self) -> &'static str { + match self { + AttachedFace::Floor => "floor", + AttachedFace::Wall => "wall", + AttachedFace::Ceiling => "ceiling", + } + } + + pub fn offset(self) -> usize { + match self { + AttachedFace::Floor => 0, + AttachedFace::Wall => 1, + AttachedFace::Ceiling => 2, + } + } + + pub fn data_with_facing(self, facing: Direction) -> Option { + Some(match (self, facing) { + (AttachedFace::Ceiling, Direction::East) => 0, + (AttachedFace::Wall, Direction::East) => 1, + (AttachedFace::Wall, Direction::West) => 2, + (AttachedFace::Wall, Direction::South) => 3, + (AttachedFace::Wall, Direction::North) => 4, + (AttachedFace::Floor, Direction::South) => 5, + (AttachedFace::Floor, Direction::East) => 6, + (AttachedFace::Ceiling, Direction::South) => 7, + _ => return None, + }) + } + + pub fn data_with_facing_and_powered(self, facing: Direction, powered: bool) -> Option { + if let Some(facing_data) = self.data_with_facing(facing) { + Some(facing_data | (if powered { 0x8 } else { 0x0 })) + } else { + None + } + } + + pub fn variant_with_facing(self, facing: Direction) -> String { + match (self, facing) { + (AttachedFace::Ceiling, Direction::East) => "down_x", + (AttachedFace::Wall, Direction::East) => "east", + (AttachedFace::Wall, Direction::West) => "west", + (AttachedFace::Wall, Direction::South) => "south", + (AttachedFace::Wall, Direction::North) => "north", + (AttachedFace::Floor, Direction::South) => "up_z", + (AttachedFace::Floor, Direction::East) => "up_x", + (AttachedFace::Ceiling, Direction::South) => "down_z", + _ => "north", // TODO: support 1.13.2+ new directions + }.to_owned() + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum ChestType { + Single, + Left, + Right, +} + +impl ChestType { + pub fn as_string(self) -> &'static str { + match self { + ChestType::Single => "single", + ChestType::Left => "left", + ChestType::Right => "right", + } + } + + pub fn offset(self) -> usize { + match self { + ChestType::Single => 0, + ChestType::Left => 1, + ChestType::Right => 2, + } + } } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] @@ -5625,7 +6977,13 @@ pub enum TreeVariant { Birch, Jungle, Acacia, - DarkOak + DarkOak, + StrippedSpruce, + StrippedBirch, + StrippedJungle, + StrippedAcacia, + StrippedDarkOak, + StrippedOak, } impl TreeVariant { @@ -5637,6 +6995,12 @@ impl TreeVariant { TreeVariant::Jungle => "jungle", TreeVariant::Acacia => "acacia", TreeVariant::DarkOak => "dark_oak", + TreeVariant::StrippedSpruce => "stripped_spruce_log", + TreeVariant::StrippedBirch => "stripped_birch_log", + TreeVariant::StrippedJungle => "stripped_jungle_log", + TreeVariant::StrippedAcacia => "stripped_acacia_log", + TreeVariant::StrippedDarkOak => "stripped_dark_oak_log", + TreeVariant::StrippedOak => "stripped_oak_log" } } @@ -5646,6 +7010,24 @@ impl TreeVariant { TreeVariant::Spruce | TreeVariant::DarkOak => 1, TreeVariant::Birch => 2, TreeVariant::Jungle => 3, + _ => panic!("TreeVariant {:?} has no data (1.13+ only)"), + } + } + + pub fn offset(self) -> usize { + match self { + TreeVariant::Oak => 0, + TreeVariant::Spruce => 1, + TreeVariant::Birch => 2, + TreeVariant::Jungle => 3, + TreeVariant::Acacia => 4, + TreeVariant::DarkOak => 5, + TreeVariant::StrippedSpruce => 6, + TreeVariant::StrippedBirch => 7, + TreeVariant::StrippedJungle => 8, + TreeVariant::StrippedAcacia => 9, + TreeVariant::StrippedDarkOak => 10, + TreeVariant::StrippedOak => 11, } } @@ -5657,6 +7039,7 @@ impl TreeVariant { TreeVariant::Jungle => 3, TreeVariant::Acacia => 4, TreeVariant::DarkOak => 5, + _ => panic!("TreeVariant {:?} has no plank data (1.13+ only)"), } } } @@ -5686,6 +7069,28 @@ impl TallGrassVariant { } } +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum TallSeagrassHalf { + Upper, + Lower, +} + +impl TallSeagrassHalf { + pub fn as_string(self) -> &'static str { + match self { + TallSeagrassHalf::Upper => "upper", + TallSeagrassHalf::Lower => "lower", + } + } + + fn offset(self) -> usize { + match self { + TallSeagrassHalf::Upper => 0, + TallSeagrassHalf::Lower => 1, + } + } +} + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum DoublePlantVariant { Sunflower, @@ -5718,6 +7123,17 @@ impl DoublePlantVariant { DoublePlantVariant::Peony => 5, } } + + pub fn offset(self) -> usize { + match self { + DoublePlantVariant::Sunflower => 0, + DoublePlantVariant::Lilac => 1, + DoublePlantVariant::RoseBush => 2, + DoublePlantVariant::Peony => 3, + DoublePlantVariant::DoubleTallgrass => 4, + DoublePlantVariant::LargeFern => 5, + } + } } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] @@ -5735,7 +7151,7 @@ pub enum FlowerPotVariant { DeadBush, Fern, AcaciaSapling, - DarkOak, + DarkOakSapling, BlueOrchid, Allium, AzureBluet, @@ -5762,7 +7178,7 @@ impl FlowerPotVariant { FlowerPotVariant::DeadBush => "dead_bush", FlowerPotVariant::Fern => "fern", FlowerPotVariant::AcaciaSapling => "acacia_sapling", - FlowerPotVariant::DarkOak => "dark_oak_sapling", + FlowerPotVariant::DarkOakSapling => "dark_oak_sapling", FlowerPotVariant::BlueOrchid => "blue_orchid", FlowerPotVariant::Allium => "allium", FlowerPotVariant::AzureBluet => "houstonia", @@ -5773,4 +7189,78 @@ impl FlowerPotVariant { FlowerPotVariant::Oxeye => "oxeye_daisy", } } + + pub fn offset(self) -> usize { + match self { + FlowerPotVariant::Empty => 0, + FlowerPotVariant::OakSapling => 1, + FlowerPotVariant::SpruceSapling => 2, + FlowerPotVariant::BirchSapling => 3, + FlowerPotVariant::JungleSapling => 4, + FlowerPotVariant::AcaciaSapling => 5, + FlowerPotVariant::DarkOakSapling => 6, + FlowerPotVariant::Fern => 7, + FlowerPotVariant::Dandelion => 8, + FlowerPotVariant::Poppy => 9, + FlowerPotVariant::BlueOrchid => 10, + FlowerPotVariant::Allium => 11, + FlowerPotVariant::AzureBluet => 12, + FlowerPotVariant::RedTulip => 13, + FlowerPotVariant::OrangeTulip => 14, + FlowerPotVariant::WhiteTulip => 15, + FlowerPotVariant::PinkTulip => 16, + FlowerPotVariant::Oxeye => 17, + FlowerPotVariant::RedMushroom => 18, + FlowerPotVariant::BrownMushroom => 19, + FlowerPotVariant::DeadBush => 20, + FlowerPotVariant::Cactus => 21, + } + } } + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum CoralVariant { + DeadTube, + DeadBrain, + DeadBubble, + DeadFire, + DeadHorn, + Tube, + Brain, + Bubble, + Fire, + Horn, +} + +impl CoralVariant { + pub fn as_string(self) -> &'static str { + match self { + CoralVariant::DeadTube => "dead_tube", + CoralVariant::DeadBrain => "dead_brain", + CoralVariant::DeadBubble => "dead_bubble", + CoralVariant::DeadFire => "dead_fire", + CoralVariant::DeadHorn => "dead_horn", + CoralVariant::Tube => "dead_tube", + CoralVariant::Brain => "brain", + CoralVariant::Bubble => "bubble", + CoralVariant::Fire => "fire", + CoralVariant::Horn => "horn", + } + } + + pub fn offset(self) -> usize { + match self { + CoralVariant::DeadTube => 0, + CoralVariant::DeadBrain => 1, + CoralVariant::DeadBubble => 2, + CoralVariant::DeadFire => 3, + CoralVariant::DeadHorn => 4, + CoralVariant::Tube => 5, + CoralVariant::Brain => 6, + CoralVariant::Bubble => 7, + CoralVariant::Fire => 8, + CoralVariant::Horn => 9, + } + } +} + diff --git a/shared/src/direction.rs b/shared/src/direction.rs index 0c6b7cd..273dc98 100644 --- a/shared/src/direction.rs +++ b/shared/src/direction.rs @@ -105,6 +105,19 @@ impl Direction { } } + pub fn offset(&self) -> usize { + match *self { + Direction::North => 0, + Direction::East => 1, + Direction::South => 2, + Direction::West => 3, + Direction::Up => 4, + Direction::Down => 5, + _ => unreachable!(), + } + } + + pub fn horizontal_index(&self) -> usize { match *self { Direction::North => 2, @@ -115,6 +128,16 @@ impl Direction { } } + pub fn horizontal_offset(&self) -> usize { + match *self { + Direction::North => 0, + Direction::South => 1, + Direction::West => 2, + Direction::East => 3, + _ => unreachable!(), + } + } + pub fn axis(&self) -> Axis { match *self { Direction::Down | Direction::Up => Axis::Y, diff --git a/src/entity/block_entity/sign.rs b/src/entity/block_entity/sign.rs index 27fa73c..8171fbf 100644 --- a/src/entity/block_entity/sign.rs +++ b/src/entity/block_entity/sign.rs @@ -92,7 +92,7 @@ impl ecs::System for SignRenderer { let info = m.get_component_mut(e, self.sign_info).unwrap(); info.dirty = false; match world.get_block(position) { - Block::WallSign{facing} => { + Block::WallSign{facing, ..} => { info.offset_z = 7.5 / 16.0; match facing { Direction::North => {}, @@ -102,7 +102,7 @@ impl ecs::System for SignRenderer { _ => unreachable!(), } }, - Block::StandingSign{rotation} => { + Block::StandingSign{rotation, ..} => { info.offset_y = 5.0 / 16.0; info.has_stand = true; info.rotation = -(rotation.data() as f64 / 16.0) * PI * 2.0 + PI; diff --git a/src/item.rs b/src/item.rs index 7ed18d6..9d791e9 100644 --- a/src/item.rs +++ b/src/item.rs @@ -21,7 +21,7 @@ use byteorder::{BigEndian, WriteBytesExt, ReadBytesExt}; pub struct Stack { id: isize, count: isize, - damage: isize, + damage: Option, tag: Option, } @@ -31,7 +31,7 @@ impl Default for Stack { Stack { id: -1, count: 0, - damage: 0, + damage: None, tag: None, } } @@ -39,14 +39,31 @@ impl Default for Stack { impl Serializable for Option { fn read_from(buf: &mut R) -> Result, protocol::Error> { - let id = buf.read_i16::()?; + let protocol_version = unsafe { protocol::CURRENT_PROTOCOL_VERSION }; + + if protocol_version >= 404 { + let present = buf.read_u8()? != 0; + if !present { + return Ok(None) + } + } + + let id = if protocol_version >= 404 { + protocol::VarInt::read_from(buf)?.0 as isize + } else { + buf.read_i16::()? as isize + }; + if id == -1 { return Ok(None); } let count = buf.read_u8()? as isize; - let damage = buf.read_i16::()? as isize; - - let protocol_version = unsafe { protocol::CURRENT_PROTOCOL_VERSION }; + let damage = if protocol_version >= 404 { + // 1.13.2+ stores damage in the NBT + None + } else { + Some(buf.read_i16::()? as isize) + }; let tag: Option = if protocol_version >= 47 { Serializable::read_from(buf)? @@ -74,9 +91,10 @@ impl Serializable for Option { fn write_to(&self, buf: &mut W) -> Result<(), protocol::Error> { match *self { Some(ref val) => { + // TODO: if protocol_version >= 404, send present and id varint, no damage, for 1.13.2 buf.write_i16::(val.id as i16)?; buf.write_u8(val.count as u8)?; - buf.write_i16::(val.damage as i16)?; + buf.write_i16::(val.damage.unwrap_or(0) as i16)?; // TODO: compress zlib NBT if 1.7 val.tag.write_to(buf)?; } diff --git a/src/model/mod.rs b/src/model/mod.rs index f9bb0c1..097e4b2 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -828,14 +828,14 @@ struct BlockFace { tint_index: i32, } -#[derive(Clone)] +#[derive(Clone, Debug)] struct Model { faces: Vec, ambient_occlusion: bool, weight: f64, } -#[derive(Clone)] +#[derive(Clone, Debug)] struct Face { cull_face: Direction, facing: Direction, @@ -1028,7 +1028,7 @@ pub const PRECOMPUTED_VERTS: [&[BlockVertex; 4]; 6] = [ ], ]; -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct BlockVertex { pub x: f32, pub y: f32, diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index c94640c..b54cea6 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -37,7 +37,7 @@ use flate2::Compression; use std::time::{Instant, Duration}; use crate::shared::Position; -pub const SUPPORTED_PROTOCOLS: [i32; 9] = [340, 316, 315, 210, 109, 107, 74, 47, 5]; +pub const SUPPORTED_PROTOCOLS: [i32; 10] = [404, 340, 316, 315, 210, 109, 107, 74, 47, 5]; // TODO: switch to using thread_local storage?, see https://doc.rust-lang.org/std/macro.thread_local.html pub static mut CURRENT_PROTOCOL_VERSION: i32 = SUPPORTED_PROTOCOLS[0]; @@ -553,6 +553,17 @@ impl fmt::Debug for LenPrefixedBytes { } } +impl Lengthable for bool { + fn into(self) -> usize { + if self { 1 } else { 0 } + } + + fn from(u: usize) -> bool { + u != 0 + } +} + + impl Lengthable for u8 { fn into(self) -> usize { self as usize diff --git a/src/protocol/packet.rs b/src/protocol/packet.rs index 4134491..9f5c2e1 100644 --- a/src/protocol/packet.rs +++ b/src/protocol/packet.rs @@ -53,6 +53,10 @@ state_packets!( packet TeleportConfirm { field teleport_id: VarInt =, } + packet QueryBlockNBT { + field transaction_id: VarInt =, + field location: Position =, + } /// TabComplete is sent by the client when the client presses tab in /// the chat box. packet TabComplete { @@ -156,6 +160,15 @@ state_packets!( field channel: String =, field data: LenPrefixedBytes =, } + packet EditBook { + field new_book: Option =, + field is_signing: bool =, + field hand: VarInt =, + } + packet QueryEntityNBT { + field transaction_id: VarInt =, + field entity_id: VarInt =, + } /// UseEntity is sent when the user interacts (right clicks) or attacks /// (left clicks) an entity. packet UseEntity { @@ -240,10 +253,13 @@ state_packets!( field yaw: f32 =, field pitch: f32 =, } - /// TODO: Document + /// SteerBoat is used to visually update the boat paddles. packet SteerBoat { - field unknown: bool =, - field unknown2: bool =, + field left_paddle_turning: bool =, + field right_paddle_turning: bool =, + } + packet PickItem { + field slot_to_use: VarInt =, } /// CraftRecipeRequest is sent when player clicks a recipe in the crafting book. packet CraftRecipeRequest { @@ -308,6 +324,9 @@ state_packets!( field crafting_book_open: bool = when(|p: &CraftingBookData| p.action.0 == 1), field crafting_filter: bool = when(|p: &CraftingBookData| p.action.0 == 1), } + packet NameItem { + field item_name: String =, + } /// ResourcePackStatus informs the server of the client's current progress /// in activating the requested resource pack packet ResourcePackStatus { @@ -322,17 +341,53 @@ state_packets!( field action: VarInt =, field tab_id: String = when(|p: &AdvancementTab| p.action.0 == 0), } + packet SelectTrade { + field selected_slot: VarInt =, + } + packet SetBeaconEffect { + field primary_effect: VarInt =, + field secondary_effect: VarInt =, + } /// HeldItemChange is sent when the player changes the currently active /// hotbar slot. packet HeldItemChange { field slot: i16 =, } + packet UpdateCommandBlock { + field location: Position =, + field command: String =, + field mode: VarInt =, + field flags: u8 =, + } + packet UpdateCommandBlockMinecart { + field entity_id: VarInt =, + field command: String =, + field track_output: bool =, + } /// CreativeInventoryAction is sent when the client clicks in the creative /// inventory. This is used to spawn items in creative. packet CreativeInventoryAction { field slot: i16 =, field clicked_item: Option =, } + packet UpdateStructureBlock { + field location: Position =, + field action: VarInt =, + field mode: VarInt =, + field name: String =, + field offset_x: i8 =, + field offset_y: i8 =, + field offset_z: i8 =, + field size_x: i8 =, + field size_y: i8 =, + field size_z: i8 =, + field mirror: VarInt =, + field rotation: VarInt =, + field metadata: String =, + field integrity: f32 =, + field seed: VarLong =, + field flags: i8 =, + } /// SetSign sets the text on a sign after placing it. packet SetSign { field location: Position =, @@ -704,6 +759,10 @@ state_packets!( packet TabCompleteReply { field matches: LenPrefixed =, } + packet DeclareCommands { + field nodes: LenPrefixed =, + field root_index: VarInt =, + } /// ServerMessage is a message sent by the server. It could be from a player /// or just a system message. The Type field controls the location the /// message is displayed at and when the message is displayed. @@ -1148,6 +1207,15 @@ state_packets!( field online: bool =, field ping: u16 =, } + packet FacePlayer { + field feet_eyes: VarInt =, + field target_x: f64 =, + field target_y: f64 =, + field target_z: f64 =, + field is_entity: bool =, + field entity_id: Option = when(|p: &FacePlayer| p.is_entity), + field entity_feet_eyes: Option = when(|p: &FacePlayer| p.is_entity), + } /// TeleportPlayer is sent to change the player's position. The client is expected /// to reply to the server with the same positions as contained in this packet /// otherwise will reject future packets. @@ -1179,12 +1247,21 @@ state_packets!( field y: u8 =, field z: i32 =, } - packet UnlockRecipes { + packet UnlockRecipes_NoSmelting { field action: VarInt =, field crafting_book_open: bool =, field filtering_craftable: bool =, field recipe_ids: LenPrefixed =, - field recipe_ids2: LenPrefixed = when(|p: &UnlockRecipes| p.action.0 == 0), + field recipe_ids2: LenPrefixed = when(|p: &UnlockRecipes_NoSmelting| p.action.0 == 0), + } + packet UnlockRecipes_WithSmelting { + field action: VarInt =, + field crafting_book_open: bool =, + field filtering_craftable: bool =, + field smelting_book_open: bool =, + field filtering_smeltable: bool =, + field recipe_ids: LenPrefixed =, + field recipe_ids2: LenPrefixed = when(|p: &UnlockRecipes_WithSmelting| p.action.0 == 0), } /// EntityDestroy destroys the entities with the ids in the provided slice. packet EntityDestroy { @@ -1229,6 +1306,10 @@ state_packets!( field entity_id: i32 =, field entity_status: i8 =, } + packet NBTQueryResponse { + field transaction_id: VarInt =, + field nbt: Option =, + } /// SelectAdvancementTab indicates the client should switch the advancement tab. packet SelectAdvancementTab { field has_id: bool =, @@ -1405,6 +1486,11 @@ state_packets!( field world_age: i64 =, field time_of_day: i64 =, } + packet StopSound { + field flags: u8 =, + field source: Option = when(|p: &StopSound| p.flags & 0x01 != 0), + field sound: Option = when(|p: &StopSound| p.flags & 0x02 != 0), + } /// Title configures an on-screen title. packet Title { field action: VarInt =, @@ -1544,6 +1630,14 @@ state_packets!( field amplifier: i8 =, field duration: i16 =, } + packet DeclareRecipes { + field recipes: LenPrefixed =, + } + packet Tags { + field block_tags: LenPrefixed =, + field item_tags: LenPrefixed =, + field fluid_tags: LenPrefixed =, + } } } login Login { @@ -1569,6 +1663,11 @@ state_packets!( field shared_secret: LenPrefixedBytes =, field verify_token: LenPrefixedBytes =, } + packet LoginPluginResponse { + field message_id: VarInt =, + field successful: bool =, + field data: Vec =, + } } clientbound Clientbound { /// LoginDisconnect is sent by the server if there was any issues @@ -1609,6 +1708,11 @@ state_packets!( /// Threshold where a packet should be sent compressed field threshold: VarInt =, } + packet LoginPluginRequest { + field message_id: VarInt =, + field channel: String =, + field data: Vec =, + } } } status Status { @@ -2165,3 +2269,324 @@ pub struct PlayerProperty { pub value: String, pub signature: Option, } + +use crate::item; +type RecipeIngredient = LenPrefixed>; + +#[derive(Debug)] +pub enum RecipeData { + Shapeless { + group: String, + ingredients: LenPrefixed, + result: Option, + }, + Shaped { + width: VarInt, + height: VarInt, + group: String, + ingredients: Vec, + result: Option, + }, + ArmorDye, + BookCloning, + MapCloning, + MapExtending, + FireworkRocket, + FireworkStar, + FireworkStarFade, + RepairItem, + TippedArrow, + BannerDuplicate, + BannerAddPattern, + ShieldDecoration, + ShulkerBoxColoring, + Smelting { + group: String, + ingredient: RecipeIngredient, + result: Option, + experience: f32, + cooking_time: VarInt, + }, +} + +impl Default for RecipeData { + fn default() -> Self { + RecipeData::ArmorDye + } +} + +#[derive(Debug, Default)] +pub struct Recipe { + pub id: String, + pub ty: String, + pub data: RecipeData, +} + +impl Serializable for Recipe { + fn read_from(buf: &mut R) -> Result { + let id = String::read_from(buf)?; + let ty = String::read_from(buf)?; + + let data = + match ty.as_ref() { + "crafting_shapeless" => RecipeData::Shapeless { + group: Serializable::read_from(buf)?, + ingredients: Serializable::read_from(buf)?, + result: Serializable::read_from(buf)?, + }, + "crafting_shaped" => { + let width: VarInt = Serializable::read_from(buf)?; + let height: VarInt = Serializable::read_from(buf)?; + let group: String = Serializable::read_from(buf)?; + + let capacity = width.0 as usize * height.0 as usize; + + let mut ingredients = Vec::with_capacity(capacity); + for _ in 0 .. capacity { + ingredients.push(Serializable::read_from(buf)?); + } + let result: Option = Serializable::read_from(buf)?; + + RecipeData::Shaped { width, height, group, ingredients, result } + } + "crafting_special_armordye" => RecipeData::ArmorDye, + "crafting_special_bookcloning" => RecipeData::BookCloning, + "crafting_special_mapcloning" => RecipeData::MapCloning, + "crafting_special_mapextending" => RecipeData::MapExtending, + "crafting_special_firework_rocket" => RecipeData::FireworkRocket, + "crafting_special_firework_star" => RecipeData::FireworkStar, + "crafting_special_firework_star_fade" => RecipeData::FireworkStarFade, + "crafting_special_repairitem" => RecipeData::RepairItem, + "crafting_special_tippedarrow" => RecipeData::TippedArrow, + "crafting_special_bannerduplicate" => RecipeData::BannerDuplicate, + "crafting_special_banneraddpattern" => RecipeData::BannerAddPattern, + "crafting_special_shielddecoration" => RecipeData::ShieldDecoration, + "crafting_special_shulkerboxcoloring" => RecipeData::ShulkerBoxColoring, + "smelting" => RecipeData::Smelting { + group: Serializable::read_from(buf)?, + ingredient: Serializable::read_from(buf)?, + result: Serializable::read_from(buf)?, + experience: Serializable::read_from(buf)?, + cooking_time: Serializable::read_from(buf)?, + }, + _ => panic!("unrecognized recipe type: {}", ty) + }; + + Ok(Recipe { id, ty, data }) + } + + fn write_to(&self, _: &mut W) -> Result<(), Error> { + unimplemented!() + } +} + +#[derive(Debug, Default)] +pub struct Tags { + pub tag_name: String, + pub entries: LenPrefixed, +} + +impl Serializable for Tags { + fn read_from(buf: &mut R) -> Result { + Ok(Tags { + tag_name: Serializable::read_from(buf)?, + entries: Serializable::read_from(buf)?, + }) + } + + fn write_to(&self, _: &mut W) -> Result<(), Error> { + unimplemented!() + } +} + +#[derive(Debug, Default)] +pub struct CommandNode { + pub flags: u8, + pub children: LenPrefixed, + pub redirect_node: Option, + pub name: Option, + pub parser: Option, + pub properties: Option, + pub suggestions_type: Option, +} + +#[derive(Debug, Eq, PartialEq)] +enum CommandNodeType { + Root, + Literal, + Argument, +} + +#[derive(Debug)] +pub enum CommandProperty { + Bool, + Double { + flags: u8, + min: Option, + max: Option, + }, + Float { + flags: u8, + min: Option, + max: Option, + }, + Integer { + flags: u8, + min: Option, + max: Option, + }, + String { + token_type: VarInt, + }, + Entity { + flags: u8, + }, + GameProfile, + BlockPos, + Vec3, + Vec2, + BlockState, + BlockPredicate, + ItemStack, + ItemPredicate, + Color, + Component, + Message, + Nbt, + NbtPath, + Objective, + ObjectiveCriteria, + Operation, + Particle, + Rotation, + ScoreboardSlot, + ScoreHolder { + flags: u8, + }, + Swizzle, + Team, + ItemSlot, + ResourceLocation, + MobEffect, + Function, + EntityAnchor, + Range { + decimals: bool, + }, + ItemEnchantment, +} + + +impl Serializable for CommandNode { + fn read_from(buf: &mut R) -> Result { + let flags: u8 = Serializable::read_from(buf)?; + let children: LenPrefixed = Serializable::read_from(buf)?; + + let node_type = match flags & 0x03 { + 0 => CommandNodeType::Root, + 1 => CommandNodeType::Literal, + 2 => CommandNodeType::Argument, + _ => panic!("unrecognized command node type {}", flags & 0x03), + }; + let _is_executable = flags & 0x04 != 0; + let has_redirect = flags & 0x08 != 0; + let has_suggestions_type = flags & 0x10 != 0; + + let redirect_node: Option = if has_redirect { + Some(Serializable::read_from(buf)?) + } else { + None + }; + + let name: Option = if node_type == CommandNodeType::Argument || node_type == CommandNodeType::Literal { + Serializable::read_from(buf)? + } else { + None + }; + let parser: Option = if node_type == CommandNodeType::Argument { + Serializable::read_from(buf)? + } else { + None + }; + + let properties: Option = if let Some(ref parse) = parser { + Some(match parse.as_ref() { + "brigadier:bool" => CommandProperty::Bool, + "brigadier:double" => { + let flags = Serializable::read_from(buf)?; + let min = if flags & 0x01 != 0 { Some(Serializable::read_from(buf)?) } else { None }; + let max = if flags & 0x02 != 0 { Some(Serializable::read_from(buf)?) } else { None }; + CommandProperty::Double { flags, min, max } + }, + "brigadier:float" => { + let flags = Serializable::read_from(buf)?; + let min = if flags & 0x01 != 0 { Some(Serializable::read_from(buf)?) } else { None }; + let max = if flags & 0x02 != 0 { Some(Serializable::read_from(buf)?) } else { None }; + CommandProperty::Float { flags, min, max } + }, + "brigadier:integer" => { + let flags = Serializable::read_from(buf)?; + let min = if flags & 0x01 != 0 { Some(Serializable::read_from(buf)?) } else { None }; + let max = if flags & 0x02 != 0 { Some(Serializable::read_from(buf)?) } else { None }; + CommandProperty::Integer { flags, min, max } + }, + "brigadier:string" => { + CommandProperty::String { token_type: Serializable::read_from(buf)? } + }, + "minecraft:entity" => { + CommandProperty::Entity { flags: Serializable::read_from(buf)? } + }, + "minecraft:game_profile" => CommandProperty::GameProfile, + "minecraft:block_pos" => CommandProperty::BlockPos, + "minecraft:vec3" => CommandProperty::Vec3, + "minecraft:vec2" => CommandProperty::Vec2, + "minecraft:block_state" => CommandProperty::BlockState, + "minecraft:block_predicate" => CommandProperty::BlockPredicate, + "minecraft:item_stack" => CommandProperty::ItemStack, + "minecraft:item_predicate" => CommandProperty::ItemPredicate, + "minecraft:color" => CommandProperty::Color, + "minecraft:component" => CommandProperty::Component, + "minecraft:message" => CommandProperty::Message, + "minecraft:nbt" => CommandProperty::Nbt, + "minecraft:nbt_path" => CommandProperty::NbtPath, + "minecraft:objective" => CommandProperty::Objective, + "minecraft:objective_criteria" => CommandProperty::ObjectiveCriteria, + "minecraft:operation" => CommandProperty::Operation, + "minecraft:particle" => CommandProperty::Particle, + "minecraft:rotation" => CommandProperty::Rotation, + "minecraft:scoreboard_slot" => CommandProperty::ScoreboardSlot, + "minecraft:score_holder" => { + CommandProperty::ScoreHolder { flags: Serializable::read_from(buf)? } + }, + "minecraft:swizzle" => CommandProperty::Swizzle, + "minecraft:team" => CommandProperty::Team, + "minecraft:item_slot" => CommandProperty::ItemSlot, + "minecraft:resource_location" => CommandProperty::ResourceLocation, + "minecraft:mob_effect" => CommandProperty::MobEffect, + "minecraft:function" => CommandProperty::Function, + "minecraft:entity_anchor" => CommandProperty::EntityAnchor, + "minecraft:range" => { + CommandProperty::Range { decimals: Serializable::read_from(buf)? } + }, + "minecraft:item_enchantment" => CommandProperty::ItemEnchantment, + _ => panic!("unsupported command node parser {}", parse), + }) + } else { + None + }; + + let suggestions_type: Option = if has_suggestions_type { + Serializable::read_from(buf)? + } else { + None + }; + + Ok(CommandNode { flags, children, redirect_node, name, parser, properties, suggestions_type }) + } + + fn write_to(&self, _: &mut W) -> Result<(), Error> { + unimplemented!() + } +} + + diff --git a/src/protocol/versions.rs b/src/protocol/versions.rs index 2d022fc..5435189 100644 --- a/src/protocol/versions.rs +++ b/src/protocol/versions.rs @@ -1,5 +1,6 @@ use crate::protocol::*; +mod v1_13_2; mod v1_12_2; mod v1_11_2; mod v1_10_2; @@ -13,6 +14,9 @@ pub fn translate_internal_packet_id_for_version(version: i32, state: State, dir: match version { // https://wiki.vg/Protocol_History // https://wiki.vg/Protocol_version_numbers#Versions_after_the_Netty_rewrite + // 1.13.2 + 404 => v1_13_2::translate_internal_packet_id(state, dir, id, to_internal), + // 1.12.2 340 => v1_12_2::translate_internal_packet_id(state, dir, id, to_internal), diff --git a/src/protocol/versions/v1_12_2.rs b/src/protocol/versions/v1_12_2.rs index c609790..7dc1454 100644 --- a/src/protocol/versions/v1_12_2.rs +++ b/src/protocol/versions/v1_12_2.rs @@ -92,7 +92,7 @@ protocol_packet_ids!( 0x2e => PlayerInfo 0x2f => TeleportPlayer_WithConfirm 0x30 => EntityUsedBed - 0x31 => UnlockRecipes + 0x31 => UnlockRecipes_NoSmelting 0x32 => EntityDestroy 0x33 => EntityRemoveEffect 0x34 => ResourcePackSend diff --git a/src/protocol/versions/v1_13_2.rs b/src/protocol/versions/v1_13_2.rs new file mode 100644 index 0000000..3f3c9da --- /dev/null +++ b/src/protocol/versions/v1_13_2.rs @@ -0,0 +1,169 @@ +protocol_packet_ids!( + handshake Handshaking { + serverbound Serverbound { + 0x00 => Handshake + } + clientbound Clientbound { + } + } + play Play { + serverbound Serverbound { + 0x00 => TeleportConfirm + 0x01 => QueryBlockNBT + 0x02 => ChatMessage + 0x03 => ClientStatus + 0x04 => ClientSettings + 0x05 => TabComplete + 0x06 => ConfirmTransactionServerbound + 0x07 => EnchantItem + 0x08 => ClickWindow + 0x09 => CloseWindow + 0x0a => PluginMessageServerbound + 0x0b => EditBook + 0x0c => QueryEntityNBT + 0x0d => UseEntity + 0x0e => KeepAliveServerbound_i64 + 0x0f => Player + 0x10 => PlayerPosition + 0x11 => PlayerPositionLook + 0x12 => PlayerLook + 0x13 => VehicleMove + 0x14 => SteerBoat + 0x15 => PickItem + 0x16 => CraftRecipeRequest + 0x17 => ClientAbilities + 0x18 => PlayerDigging + 0x19 => PlayerAction + 0x1a => SteerVehicle + 0x1b => CraftingBookData + 0x1c => NameItem + 0x1d => ResourcePackStatus + 0x1e => AdvancementTab + 0x1f => SelectTrade + 0x20 => SetBeaconEffect + 0x21 => HeldItemChange + 0x22 => UpdateCommandBlock + 0x23 => UpdateCommandBlockMinecart + 0x24 => CreativeInventoryAction + 0x25 => UpdateStructureBlock + 0x26 => SetSign + 0x27 => ArmSwing + 0x28 => SpectateTeleport + 0x29 => PlayerBlockPlacement_f32 + 0x2a => UseItem + } + clientbound Clientbound { + 0x00 => SpawnObject + 0x01 => SpawnExperienceOrb + 0x02 => SpawnGlobalEntity + 0x03 => SpawnMob + 0x04 => SpawnPainting + 0x05 => SpawnPlayer_f64 + 0x06 => Animation + 0x07 => Statistics + 0x08 => BlockBreakAnimation + 0x09 => UpdateBlockEntity + 0x0a => BlockAction + 0x0b => BlockChange_VarInt + 0x0c => BossBar + 0x0d => ServerDifficulty + 0x0e => ServerMessage + 0x0f => MultiBlockChange_VarInt + 0x10 => TabCompleteReply + 0x11 => DeclareCommands + 0x12 => ConfirmTransaction + 0x13 => WindowClose + 0x14 => WindowOpen + 0x15 => WindowItems + 0x16 => WindowProperty + 0x17 => WindowSetSlot + 0x18 => SetCooldown + 0x19 => PluginMessageClientbound + 0x1a => NamedSoundEffect + 0x1b => Disconnect + 0x1c => EntityAction + 0x1d => NBTQueryResponse + 0x1e => Explosion + 0x1f => ChunkUnload + 0x20 => ChangeGameState + 0x21 => KeepAliveClientbound_i64 + 0x22 => ChunkData + 0x23 => Effect + 0x24 => Particle + 0x25 => JoinGame_i32 + 0x26 => Maps + 0x27 => Entity + 0x28 => EntityMove_i16 + 0x29 => EntityLookAndMove_i16 + 0x2a => EntityLook_VarInt + 0x2b => VehicleTeleport + 0x2c => SignEditorOpen + 0x2d => CraftRecipeResponse + 0x2e => PlayerAbilities + 0x2f => CombatEvent + 0x30 => PlayerInfo + 0x31 => FacePlayer + 0x32 => TeleportPlayer_WithConfirm + 0x33 => EntityUsedBed + 0x34 => UnlockRecipes_WithSmelting + 0x35 => EntityDestroy + 0x36 => EntityRemoveEffect + 0x37 => ResourcePackSend + 0x38 => Respawn + 0x39 => EntityHeadLook + 0x3a => SelectAdvancementTab + 0x3b => WorldBorder + 0x3c => Camera + 0x3d => SetCurrentHotbarSlot + 0x3e => ScoreboardDisplay + 0x3f => EntityMetadata + 0x40 => EntityAttach + 0x41 => EntityVelocity + 0x42 => EntityEquipment + 0x43 => SetExperience + 0x44 => UpdateHealth + 0x45 => ScoreboardObjective + 0x46 => SetPassengers + 0x47 => Teams + 0x48 => UpdateScore + 0x49 => SpawnPosition + 0x4a => TimeUpdate + 0x4c => StopSound + 0x4d => SoundEffect + 0x4e => PlayerListHeaderFooter + 0x4f => CollectItem + 0x50 => EntityTeleport_f64 + 0x51 => Advancements + 0x52 => EntityProperties + 0x53 => EntityEffect + 0x54 => DeclareRecipes + 0x55 => Tags + } + } + login Login { + serverbound Serverbound { + 0x00 => LoginStart + 0x01 => EncryptionResponse + 0x02 => LoginPluginResponse + } + clientbound Clientbound { + 0x00 => LoginDisconnect + 0x01 => EncryptionRequest + 0x02 => LoginSuccess + 0x03 => SetInitialCompression + 0x04 => LoginPluginRequest + } + } + status Status { + serverbound Serverbound { + 0x00 => StatusRequest + 0x01 => StatusPing + } + clientbound Clientbound { + 0x00 => StatusResponse + 0x01 => StatusPong + } + } +); + + diff --git a/src/server/mod.rs b/src/server/mod.rs index 0aacee0..3bbdc4c 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -242,13 +242,13 @@ impl Server { if xx == 0 && z == 0 { continue; } - server.world.set_block(Position::new(x + xx, h + 3, z + zz), block::Leaves{ variant: block::TreeVariant::Oak, check_decay: false, decayable: false }); - server.world.set_block(Position::new(x + xx, h + 4, z + zz), block::Leaves{ variant: block::TreeVariant::Oak, check_decay: false, decayable: false }); + server.world.set_block(Position::new(x + xx, h + 3, z + zz), block::Leaves{ variant: block::TreeVariant::Oak, check_decay: false, decayable: false, distance: 1 }); + server.world.set_block(Position::new(x + xx, h + 4, z + zz), block::Leaves{ variant: block::TreeVariant::Oak, check_decay: false, decayable: false, distance: 1 }); if xx.abs() <= 1 && zz.abs() <= 1 { - server.world.set_block(Position::new(x + xx, h + 5, z + zz), block::Leaves{ variant: block::TreeVariant::Oak, check_decay: false, decayable: false }); + server.world.set_block(Position::new(x + xx, h + 5, z + zz), block::Leaves{ variant: block::TreeVariant::Oak, check_decay: false, decayable: false, distance: 1 }); } if xx * xx + zz * zz <= 1 { - server.world.set_block(Position::new(x + xx, h + 6, z + zz), block::Leaves{ variant: block::TreeVariant::Oak, check_decay: false, decayable: false }); + server.world.set_block(Position::new(x + xx, h + 6, z + zz), block::Leaves{ variant: block::TreeVariant::Oak, check_decay: false, decayable: false, distance: 1 }); } } } @@ -280,7 +280,7 @@ impl Server { disconnect_reason: None, just_disconnected: false, - world: world::World::new(), + world: world::World::new(protocol_version), world_age: 0, world_time: 0.0, world_time_target: 0.0, @@ -701,7 +701,7 @@ impl Server { } fn on_respawn(&mut self, respawn: packet::play::clientbound::Respawn) { - self.world = world::World::new(); + self.world = world::World::new(self.protocol_version); let gamemode = Gamemode::from_int((respawn.gamemode & 0x7) as i32); if let Some(player) = self.player { @@ -1155,7 +1155,7 @@ impl Server { } fn on_block_change(&mut self, location: Position, id: i32) { - self.world.set_block(location, block::Block::by_vanilla_id(id as usize)) + self.world.set_block(location, block::Block::by_vanilla_id(id as usize, self.protocol_version)) } fn on_block_change_varint(&mut self, block_change: packet::play::clientbound::BlockChange_VarInt) { @@ -1179,7 +1179,7 @@ impl Server { record.y as i32, oz + (record.xz & 0xF) as i32 ), - block::Block::by_vanilla_id(record.block_id.0 as usize) + block::Block::by_vanilla_id(record.block_id.0 as usize, self.protocol_version) ); } } @@ -1202,7 +1202,7 @@ impl Server { self.world.set_block( Position::new(x, y, z), - block::Block::by_vanilla_id(id as usize) + block::Block::by_vanilla_id(id as usize, self.protocol_version) ); } } diff --git a/src/server/plugin_messages.rs b/src/server/plugin_messages.rs index c0421ab..3eb9faf 100644 --- a/src/server/plugin_messages.rs +++ b/src/server/plugin_messages.rs @@ -9,10 +9,18 @@ pub struct Brand { impl Brand { pub fn as_message(self) -> PluginMessageServerbound { + let protocol_version = unsafe { crate::protocol::CURRENT_PROTOCOL_VERSION }; + + let channel_name = if protocol_version >= 404 { + "minecraft:brand" + } else { + "MC|Brand" + }; + let mut data = vec![]; Serializable::write_to(&self.brand, &mut data).unwrap(); PluginMessageServerbound { - channel: "MC|Brand".into(), + channel: channel_name.into(), data, } } diff --git a/src/types/metadata.rs b/src/types/metadata.rs index 44ee0a7..a5e8a3e 100644 --- a/src/types/metadata.rs +++ b/src/types/metadata.rs @@ -18,6 +18,7 @@ use std::io; use std::fmt; use crate::protocol; use crate::protocol::Serializable; +use crate::protocol::LenPrefixed; use crate::format; use crate::item; use crate::shared::Position; @@ -271,13 +272,151 @@ impl Metadata { u8::write_to(&0xFF, buf)?; Ok(()) } + + fn read_from113(buf: &mut R) -> Result { + let mut m = Self::new(); + loop { + let index = u8::read_from(buf)? as i32; + if index == 0xFF { + break; + } + let ty = protocol::VarInt::read_from(buf)?.0; + match ty { + 0 => m.put_raw(index, i8::read_from(buf)?), + 1 => m.put_raw(index, protocol::VarInt::read_from(buf)?.0), + 2 => m.put_raw(index, f32::read_from(buf)?), + 3 => m.put_raw(index, String::read_from(buf)?), + 4 => m.put_raw(index, format::Component::read_from(buf)?), + 5 => m.put_raw(index, LenPrefixed::::read_from(buf)?), + 6 => m.put_raw(index, Option::::read_from(buf)?), + 7 => m.put_raw(index, bool::read_from(buf)?), + 8 => m.put_raw(index, + [f32::read_from(buf)?, + f32::read_from(buf)?, + f32::read_from(buf)?]), + 9 => m.put_raw(index, Position::read_from(buf)?), + 10 => { + if bool::read_from(buf)? { + m.put_raw(index, Option::::read_from(buf)?); + } else { + m.put_raw::>(index, None); + } + } + 11 => m.put_raw(index, protocol::VarInt::read_from(buf)?), + 12 => { + if bool::read_from(buf)? { + m.put_raw(index, Option::::read_from(buf)?); + } else { + m.put_raw::>(index, None); + } + } + 13 => m.put_raw(index, protocol::VarInt::read_from(buf)?.0 as u16), + 14 => { + let ty = u8::read_from(buf)?; + if ty != 0 { + let name = nbt::read_string(buf)?; + let tag = nbt::Tag::read_from(buf)?; + + m.put_raw(index, nbt::NamedTag(name, tag)); + } + } + 15 => panic!("TODO: particle"), + _ => return Err(protocol::Error::Err("unknown metadata type".to_owned())), + } + } + Ok(m) + } + + fn write_to113(&self, buf: &mut W) -> Result<(), protocol::Error> { + for (k, v) in &self.map { + (*k as u8).write_to(buf)?; + match *v { + Value::Byte(ref val) => { + u8::write_to(&0, buf)?; + val.write_to(buf)?; + } + Value::Int(ref val) => { + u8::write_to(&1, buf)?; + protocol::VarInt(*val).write_to(buf)?; + } + Value::Float(ref val) => { + u8::write_to(&2, buf)?; + val.write_to(buf)?; + } + Value::String(ref val) => { + u8::write_to(&3, buf)?; + val.write_to(buf)?; + } + Value::FormatComponent(ref val) => { + u8::write_to(&4, buf)?; + val.write_to(buf)?; + } + Value::OptionalFormatComponent(ref val) => { + u8::write_to(&5, buf)?; + val.write_to(buf)?; + } + Value::OptionalItemStack(ref val) => { + u8::write_to(&6, buf)?; + val.write_to(buf)?; + } + Value::Bool(ref val) => { + u8::write_to(&7, buf)?; + val.write_to(buf)?; + } + Value::Vector(ref val) => { + u8::write_to(&8, buf)?; + val[0].write_to(buf)?; + val[1].write_to(buf)?; + val[2].write_to(buf)?; + } + Value::Position(ref val) => { + u8::write_to(&9, buf)?; + val.write_to(buf)?; + } + Value::OptionalPosition(ref val) => { + u8::write_to(&10, buf)?; + val.is_some().write_to(buf)?; + val.write_to(buf)?; + } + Value::Direction(ref val) => { + u8::write_to(&11, buf)?; + val.write_to(buf)?; + } + Value::OptionalUUID(ref val) => { + u8::write_to(&12, buf)?; + val.is_some().write_to(buf)?; + val.write_to(buf)?; + } + Value::Block(ref val) => { + u8::write_to(&13, buf)?; + protocol::VarInt(*val as i32).write_to(buf)?; + } + Value::NBTTag(ref _val) => { + u8::write_to(&14, buf)?; + // TODO: write NBT tags metadata + //nbt::Tag(*val).write_to(buf)?; + } + Value::Particle(ref val) => { + u8::write_to(&15, buf)?; + val.write_to(buf)?; + } + _ => panic!("unexpected metadata"), + } + } + u8::write_to(&0xFF, buf)?; + Ok(()) + } + + } impl Serializable for Metadata { fn read_from(buf: &mut R) -> Result { let protocol_version = unsafe { protocol::CURRENT_PROTOCOL_VERSION }; - if protocol_version >= 74 { + if protocol_version >= 404 { + Metadata::read_from113(buf) + } else if protocol_version >= 74 { Metadata::read_from19(buf) } else { Metadata::read_from18(buf) @@ -287,7 +426,9 @@ impl Serializable for Metadata { fn write_to(&self, buf: &mut W) -> Result<(), protocol::Error> { let protocol_version = unsafe { protocol::CURRENT_PROTOCOL_VERSION }; - if protocol_version >= 74 { + if protocol_version >= 404 { + self.write_to113(buf) + } else if protocol_version >= 74 { self.write_to19(buf) } else { self.write_to18(buf) @@ -319,6 +460,7 @@ pub enum Value { Float(f32), String(String), FormatComponent(format::Component), + OptionalFormatComponent(LenPrefixed), OptionalItemStack(Option), Bool(bool), Vector([f32; 3]), @@ -329,6 +471,146 @@ pub enum Value { OptionalUUID(Option), Block(u16), // TODO: Proper type NBTTag(nbt::NamedTag), + Particle(ParticleData), +} + +#[derive(Debug)] +pub enum ParticleData { + AmbientEntityEffect, + AngryVillager, + Barrier, + Block { + block_state: protocol::VarInt, + }, + Bubble, + Cloud, + Crit, + DamageIndicator, + DragonBreath, + DrippingLava, + DrippingWater, + Dust { + red: f32, + green: f32, + blue: f32, + scale: f32, + }, + Effect, + ElderGuardian, + EnchantedHit, + Enchant, + EndRod, + EntityEffect, + ExplosionEmitter, + Explosion, + FallingDust { + block_state: protocol::VarInt, + }, + Firework, + Fishing, + Flame, + HappyVillager, + Heart, + InstantEffect, + Item { + item: Option, + }, + ItemSlime, + ItemSnowball, + LargeSmoke, + Lava, + Mycelium, + Note, + Poof, + Portal, + Rain, + Smoke, + Spit, + SquidInk, + SweepAttack, + TotemOfUndying, + Underwater, + Splash, + Witch, + BubblePop, + CurrentDown, + BubbleColumnUp, + Nautilus, + Dolphin, +} + +impl Serializable for ParticleData { + fn read_from(buf: &mut R) -> Result { + let id = protocol::VarInt::read_from(buf)?.0; + Ok(match id { + 0 => ParticleData::AmbientEntityEffect, + 1 => ParticleData::AngryVillager, + 2 => ParticleData::Barrier, + 3 => ParticleData::Block { + block_state: Serializable::read_from(buf)? + }, + 4 => ParticleData::Bubble, + 5 => ParticleData::Cloud, + 6 => ParticleData::Crit, + 7 => ParticleData::DamageIndicator, + 8 => ParticleData::DragonBreath, + 9 => ParticleData::DrippingLava, + 10 => ParticleData::DrippingWater, + 11 => ParticleData::Dust { + red: Serializable::read_from(buf)?, + green: Serializable::read_from(buf)?, + blue: Serializable::read_from(buf)?, + scale: Serializable::read_from(buf)?, + }, + 12 => ParticleData::Effect, + 13 => ParticleData::ElderGuardian, + 14 => ParticleData::EnchantedHit, + 15 => ParticleData::Enchant, + 16 => ParticleData::EndRod, + 17 => ParticleData::EntityEffect, + 18 => ParticleData::ExplosionEmitter, + 19 => ParticleData::Explosion, + 20 => ParticleData::FallingDust { + block_state: Serializable::read_from(buf)?, + }, + 21 => ParticleData::Firework, + 22 => ParticleData::Fishing, + 23 => ParticleData::Flame, + 24 => ParticleData::HappyVillager, + 25 => ParticleData::Heart, + 26 => ParticleData::InstantEffect, + 27 => ParticleData::Item { + item: Serializable::read_from(buf)?, + }, + 28 => ParticleData::ItemSlime, + 29 => ParticleData::ItemSnowball, + 30 => ParticleData::LargeSmoke, + 31 => ParticleData::Lava, + 32 => ParticleData::Mycelium, + 33 => ParticleData::Note, + 34 => ParticleData::Poof, + 35 => ParticleData::Portal, + 36 => ParticleData::Rain, + 37 => ParticleData::Smoke, + 38 => ParticleData::Spit, + 39 => ParticleData::SquidInk, + 40 => ParticleData::SweepAttack, + 41 => ParticleData::TotemOfUndying, + 42 => ParticleData::Underwater, + 43 => ParticleData::Splash, + 44 => ParticleData::Witch, + 45 => ParticleData::BubblePop, + 46 => ParticleData::CurrentDown, + 47 => ParticleData::BubbleColumnUp, + 48 => ParticleData::Nautilus, + 49 => ParticleData::Dolphin, + _ => panic!("unrecognized particle data id {}", id), + }) + } + + fn write_to(&self, _buf: &mut W) -> Result<(), protocol::Error> { + unimplemented!() + } } pub trait MetaValue { @@ -408,6 +690,19 @@ impl MetaValue for format::Component { } } +impl MetaValue for LenPrefixed { + fn unwrap(value: &Value) -> &Self { + match *value { + Value::OptionalFormatComponent(ref val) => val, + _ => panic!("incorrect key"), + } + } + fn wrap(self) -> Value { + Value::OptionalFormatComponent(self) + } +} + + impl MetaValue for Option { fn unwrap(value: &Value) -> &Self { match *value { diff --git a/src/world/mod.rs b/src/world/mod.rs index 9ded72a..9900846 100644 --- a/src/world/mod.rs +++ b/src/world/mod.rs @@ -43,6 +43,8 @@ pub struct World { light_updates: VecDeque, block_entity_actions: VecDeque, + + protocol_version: i32, } #[derive(Clone, Debug)] @@ -79,7 +81,12 @@ struct LightUpdate { } impl World { - pub fn new() -> World { Default::default() } + pub fn new(protocol_version: i32) -> World { + World { + protocol_version, + ..Default::default() + } + } pub fn is_chunk_loaded(&self, x: i32, z: i32) -> bool { self.chunks.contains_key(&CPos(x, z)) @@ -612,7 +619,7 @@ impl World { for bi in 0 .. 4096 { let id = data.read_u16::()?; - section.blocks.set(bi, block::Block::by_vanilla_id(id as usize)); + section.blocks.set(bi, block::Block::by_vanilla_id(id as usize, self.protocol_version)); // Spawn block entities let b = section.blocks.get(bi); @@ -798,7 +805,7 @@ impl World { for bi in 0 .. 4096 { let id = ((block_add[i].get(bi) as u16) << 12) | ((block_types[i][bi] as u16) << 4) | (block_meta[i].get(bi) as u16); - section.blocks.set(bi, block::Block::by_vanilla_id(id as usize)); + section.blocks.set(bi, block::Block::by_vanilla_id(id as usize, self.protocol_version)); // Spawn block entities let b = section.blocks.get(bi); @@ -870,7 +877,7 @@ impl World { let count = VarInt::read_from(&mut data)?.0; for i in 0 .. count { let id = VarInt::read_from(&mut data)?.0; - let bl = block::Block::by_vanilla_id(id as usize); + let bl = block::Block::by_vanilla_id(id as usize, self.protocol_version); mappings.insert(i as usize, bl); } } @@ -880,7 +887,7 @@ impl World { for bi in 0 .. 4096 { let id = m.get(bi); - section.blocks.set(bi, mappings.get(&id).cloned().unwrap_or(block::Block::by_vanilla_id(id))); + section.blocks.set(bi, mappings.get(&id).cloned().unwrap_or(block::Block::by_vanilla_id(id, self.protocol_version))); // Spawn block entities let b = section.blocks.get(bi); if block_entity::BlockEntityType::get_block_entity(b).is_some() {