alex/beta-minecraft-rust
Minecraft Beta 1.7.3 Rewrite
BRANCH main
SNAPSHOT Draw terrain GPU-driven from packed per-section buffers with GPU occlusion culling, add a render distance slider (2-128) with a quad/disk shape, cut chunk bookkeeping at high render distances, and fix a light underflow at y=0
DOWNLOAD
root crates server src blocks placement.rs
use common::interaction::adjacent;
use common::math::floor_f64;
use common::world::shape::{self, BlockView};
use common::world::{Block, ChunkPos, bed};
use crate::level::Level;
/// The side a torch of each orientation leans on: walls 1 to 4, the floor for 5.
const TORCH_SIDES: [u8; 6] = [0, 4, 5, 2, 3, 0];
/// `ItemBlock.onItemUse` from where the block goes: `World.canBlockBePlacedAt`, then the
/// metadata of `onBlockPlaced` and `onBlockPlacedBy` for a placer at `placer` facing `yaw`.
pub fn place(
level: &mut Level,
position: [i32; 3],
block: Block,
metadata: u8,
side: u8,
(placer, yaw): (glam::DVec3, f32),
) -> bool {
let [x, y, z] = position;
if !(0..128).contains(&y)
|| y == 127 && block.material().is_solid()
|| !level.is_loaded(ChunkPos::containing(x, z))
{
return false;
}
let current = level.block(x, y, z);
if !current.material().is_ground_cover() {
return false;
}
let Some(metadata) = placed_metadata(level, position, block, metadata, side, (placer, yaw))
else {
return false;
};
let supported = match block {
Block::SAPLING
| Block::DANDELION
| Block::ROSE
| Block::TALL_GRASS
| Block::DEAD_BUSH
| Block::BROWN_MUSHROOM
| Block::RED_MUSHROOM
| Block::WHEAT
| Block::CACTUS
| Block::SUGAR_CANE => super::plants::can_place(level, block, x, y, z),
Block::PUMPKIN | Block::JACK_O_LANTERN => super::pumpkin_can_place(level, x, y, z),
Block::SNOW_LAYER => super::snow_fits(level, x, y, z),
Block::REDSTONE_WIRE
| Block::REPEATER
| Block::STONE_PRESSURE_PLATE
| Block::WOODEN_PRESSURE_PLATE
| Block::RAIL
| Block::POWERED_RAIL
| Block::DETECTOR_RAIL => level.is_normal_cube(x, y - 1, z),
Block::CAKE => super::plants::cake_can_stay(level, x, y, z),
Block::FENCE => {
let below = level.block(x, y - 1, z);
below == Block::FENCE || below.material().is_solid()
}
Block::CHEST => {
let mut neighbors = 0;
for side in 2..6 {
let [nx, ny, nz] = adjacent(position, side).unwrap();
if level.block(nx, ny, nz) == Block::CHEST {
neighbors += 1;
for other in 2..6 {
let [cx, cy, cz] = adjacent([nx, ny, nz], other).unwrap();
if level.block(cx, cy, cz) == Block::CHEST {
return false;
}
}
}
}
neighbors <= 1
}
_ => true,
};
if !supported {
return false;
}
let boxes = shape::placement_boxes(level, block, metadata, position);
if level.entities.iter().any(|entity| {
entity.prevents_spawning() && boxes.iter().any(|b| b.intersects(&entity.entity.bb))
}) {
return false;
}
if !level.set_block_with_metadata_notify(x, y, z, block, metadata) {
return false;
}
match block {
Block::REPEATER => super::redstone::repeater_placed(level, x, y, z),
Block::PISTON | Block::STICKY_PISTON => super::piston::update(level, block, x, y, z),
_ => {}
}
true
}
/// The metadata of `onBlockPlaced` and `onBlockPlacedBy`, or `None` without a support.
fn placed_metadata(
level: &mut Level,
position @ [x, y, z]: [i32; 3],
block: Block,
metadata: u8,
side: u8,
(placer, yaw): (glam::DVec3, f32),
) -> Option<u8> {
let facing = facing(yaw);
Some(match block {
Block::FURNACE | Block::DISPENSER => [2, 5, 3, 4][facing],
Block::OAK_STAIRS | Block::COBBLESTONE_STAIRS => [2, 1, 3, 0][facing],
Block::PUMPKIN | Block::JACK_O_LANTERN => facing as u8,
Block::REPEATER => (facing as u8 + 2) % 4,
Block::PISTON | Block::STICKY_PISTON => super::piston::orientation(placer, yaw, position),
Block::LEVER | Block::BUTTON => {
super::redstone::switch_orientation(level, block, position, side)?
}
Block::LEAVES => metadata & 3 | 8,
Block::TORCH | Block::REDSTONE_TORCH | Block::UNLIT_REDSTONE_TORCH => {
let supports = |level: &Level, side| {
adjacent(position, side).is_some_and(|[sx, sy, sz]| {
let support = BlockView::block(level, sx, sy, sz);
support.is_normal_cube() || side == 0 && support == Block::FENCE
})
};
let preferred = side ^ 1;
let support = if preferred < 6 && preferred != 1 && supports(level, preferred) {
preferred
} else {
[4, 5, 2, 3, 0]
.into_iter()
.find(|&side| supports(level, side))?
};
// The inverse of `TORCH_SIDES`
[5, 0, 3, 4, 1, 2][usize::from(support)]
}
Block::TRAPDOOR => {
let [sx, sy, sz] = adjacent(position, side ^ 1).filter(|_| side >= 2)?;
level.is_normal_cube(sx, sy, sz).then_some(side - 2)?
}
// `BlockLadder.canPlaceBlockAt` and `onBlockPlaced`: the clicked wall if it holds,
// otherwise any wall beside it
Block::LADDER => {
let mut facing = None;
for (wall, [dx, dz]) in (2..).zip(super::WALLS) {
if (facing.is_none() || side == wall) && level.is_normal_cube(x + dx, y, z + dz) {
facing = Some(wall);
}
}
facing?
}
_ => metadata & 15,
})
}
/// The horizontal direction a placer looks at: south, west, north or east.
pub(super) fn facing(yaw: f32) -> usize {
(floor_f64(f64::from(yaw * 4.0 / 360.0) + 0.5) & 3) as usize
}
pub fn place_door(level: &mut Level, [x, y, z]: [i32; 3], block: Block, yaw: f32) -> bool {
let y = y + 1;
if !(1..127).contains(&y)
|| !level.is_loaded(ChunkPos::containing(x, z))
|| !level.is_normal_cube(x, y - 1, z)
|| !level.block(x, y, z).material().is_ground_cover()
|| !level.block(x, y + 1, z).material().is_ground_cover()
{
return false;
}
let mut facing = (floor_f64(f64::from((yaw + 180.0) * 4.0 / 360.0) - 0.5) & 3) as u8;
let [dx, dz] = [[0, 1], [-1, 0], [0, -1], [1, 0]][usize::from(facing)];
let mut support = |dx, dz| {
let solid = i32::from(level.is_normal_cube(x + dx, y, z + dz))
+ i32::from(level.is_normal_cube(x + dx, y + 1, z + dz));
let door =
level.block(x + dx, y, z + dz) == block || level.block(x + dx, y + 1, z + dz) == block;
(solid, door)
};
let (left, left_door) = support(-dx, -dz);
let (right, right_door) = support(dx, dz);
if left_door && !right_door || right > left {
facing = facing.wrapping_sub(1) & 3 | 4;
}
level.editing_blocks = true;
level.set_block_with_metadata_notify(x, y, z, block, facing);
level.set_block_with_metadata_notify(x, y + 1, z, block, facing | 8);
level.editing_blocks = false;
level.notify_neighbors(x, y, z, block);
level.notify_neighbors(x, y + 1, z, block);
true
}
/// `ItemBed.onItemUse` on the top face of the block at `position`.
pub fn place_bed(level: &mut Level, [x, y, z]: [i32; 3], yaw: f32) -> bool {
let y = y + 1;
let facing = facing(yaw);
let [dx, dz] = bed::HEAD_OFFSETS[facing];
if !level.is_air(x, y, z)
|| !level.is_air(x + dx, y, z + dz)
|| !level.is_normal_cube(x, y - 1, z)
|| !level.is_normal_cube(x + dx, y - 1, z + dz)
{
return false;
}
level.set_block_with_metadata_notify(x, y, z, Block::BED, facing as u8);
level.set_block_with_metadata_notify(x + dx, y, z + dz, Block::BED, facing as u8 + 8);
true
}
/// `BlockTorch.onBlockAdded` for torches without an orientation: leans on the first support.
pub fn orient_torch(level: &mut Level, block: Block, x: i32, y: i32, z: i32) {
let wall = super::SIDES
.into_iter()
.position(|[dx, dz]| level.is_normal_cube(x + dx, y, z + dz));
let metadata = match wall {
Some(wall) => wall as u8 + 1,
None if level.is_normal_cube(x, y - 1, z) || level.block(x, y - 1, z) == Block::FENCE => 5,
None => 0,
};
if metadata != 0 {
level.set_metadata_notify(x, y, z, metadata);
}
check_support(level, block, x, y, z);
}
pub fn check_support(level: &mut Level, block: Block, x: i32, y: i32, z: i32) {
let metadata = level.metadata(x, y, z);
let valid = match block {
Block::WOODEN_DOOR | Block::IRON_DOOR => {
if metadata & 8 != 0 {
level.block(x, y - 1, z) == block
} else {
level.block(x, y + 1, z) == block && level.is_normal_cube(x, y - 1, z)
}
}
Block::TORCH | Block::REDSTONE_TORCH | Block::UNLIT_REDSTONE_TORCH => {
let side = TORCH_SIDES.get(usize::from(metadata)).copied().unwrap_or(0);
let [sx, sy, sz] = adjacent([x, y, z], side).unwrap();
level.is_normal_cube(sx, sy, sz) || side == 0 && level.block(sx, sy, sz) == Block::FENCE
}
Block::LADDER => adjacent([x, y, z], metadata ^ 1)
.is_some_and(|[sx, sy, sz]| level.is_normal_cube(sx, sy, sz)),
Block::TRAPDOOR => {
let [dx, dz] = super::WALLS[usize::from(metadata & 3)];
level.is_normal_cube(x + dx, y, z + dz)
}
_ => true,
};
if !valid {
level.set_block_notify(x, y, z, Block::AIR);
super::drop_as_item(level, block, x, y, z, metadata);
}
}
/// `BlockSign.onNeighborBlockChange`: posts need solid ground, wall signs the block behind.
pub fn check_sign(level: &mut Level, block: Block, x: i32, y: i32, z: i32) {
let metadata = level.metadata(x, y, z);
let supported = if block == Block::SIGN {
level.material(x, y - 1, z).is_solid()
} else {
(2..=5).contains(&metadata) && {
let [dx, dz] = super::WALLS[usize::from(metadata - 2)];
level.material(x + dx, y, z + dz).is_solid()
}
};
if !supported {
super::drop_as_item(level, block, x, y, z, metadata);
level.set_block_notify(x, y, z, Block::AIR);
}
}
/// `ItemSign.onItemUse`: a post on top of a solid block, facing the placer, or a sign on its
/// side. Returns where it went.
pub fn place_sign(level: &mut Level, [x, y, z]: [i32; 3], side: u8, yaw: f32) -> Option<[i32; 3]> {
if !(1..=5).contains(&side) || !level.material(x, y, z).is_solid() {
return None;
}
let [dx, dy, dz] = shape::SIDE_OFFSETS[usize::from(side)];
let (x, y, z) = (x + dx, y + dy, z + dz);
let current = level.block(x, y, z);
if !(current.is_air() || current.material().is_ground_cover()) {
return None;
}
if side == 1 {
let facing = floor_f64(f64::from((yaw + 180.0) * 16.0 / 360.0) + 0.5) & 15;
level.set_block_with_metadata_notify(x, y, z, Block::SIGN, facing as u8);
} else {
level.set_block_with_metadata_notify(x, y, z, Block::WALL_SIGN, side);
}
matches!(level.block(x, y, z), Block::SIGN | Block::WALL_SIGN).then_some([x, y, z])
}