Theme

alex/beta-minecraft-rust

Minecraft Beta 1.7.3 Rewrite

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

root / crates / 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])
}