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 / devices.rs

//! Blocks that respond to redstone: `BlockDoor`, `BlockTrapDoor`, `BlockTNT`, `BlockNote` and
//! `BlockDispenser`.

use common::item::{Item, ItemStack};
use common::protocol::Packet;
use common::world::{Block, Material, TileEntity};
use glam::DVec3;

use super::{drop_as_item, redstone};
use crate::level::Level;

/// Whether a neighbor change comes from a block that can carry power.
fn power_change(level: &Level, neighbor: Block) -> bool {
    !neighbor.is_air() && redstone::can_provide_power(level, neighbor)
}

impl Level {
    /// `World.playAuxSFX`: a sound or particle effect clients play themselves.
    pub fn aux_effect(&mut self, effect: i32, [x, y, z]: [i32; 3], data: i32) {
        self.events.push(Packet::AuxEffect {
            effect,
            x,
            y: y as i8,
            z,
            data,
        });
    }

    /// `World.playNoteAt`: note blocks sound on clients, pistons move.
    pub fn play_note(&mut self, [x, y, z]: [i32; 3], instrument: u8, pitch: u8) {
        match self.block(x, y, z) {
            Block::PISTON | Block::STICKY_PISTON => {
                super::piston::play(self, [x, y, z], instrument, pitch)
            }
            Block::NOTE_BLOCK => {}
            _ => return,
        }
        self.events.push(Packet::PlayNoteBlock {
            x,
            y: y as i16,
            z,
            instrument,
            pitch,
        });
    }
}

// ---- Doors --------------------------------------------------

/// `BlockDoor.onPoweredBlockChange`: opens or closes both halves.
fn power_door(level: &mut Level, block: Block, x: i32, y: i32, z: i32, open: bool) {
    let meta = level.metadata(x, y, z);
    if meta & 8 != 0 {
        if level.block(x, y - 1, z) == block {
            power_door(level, block, x, y - 1, z, open);
        }
    } else if (meta & 4 != 0) != open {
        toggle_door(level, block, x, y, z, meta);
    }
}

/// Flips the lower half at `y` and the upper half above it.
pub fn toggle_door(level: &mut Level, block: Block, x: i32, y: i32, z: i32, meta: u8) {
    if level.block(x, y + 1, z) == block {
        level.set_metadata_notify(x, y + 1, z, (meta ^ 4) + 8);
    }
    level.set_metadata_notify(x, y, z, meta ^ 4);
    level.aux_effect(1003, [x, y, z], 0);
}

/// `BlockDoor.onNeighborBlockChange`.
pub fn door_neighbor_changed(
    level: &mut Level,
    block: Block,
    x: i32,
    y: i32,
    z: i32,
    neighbor: Block,
) {
    let meta = level.metadata(x, y, z);
    if meta & 8 != 0 {
        if level.block(x, y - 1, z) != block {
            level.set_block_notify(x, y, z, Block::AIR);
        }
        if power_change(level, neighbor) {
            door_neighbor_changed(level, block, x, y - 1, z, neighbor);
        }
        return;
    }
    let mut broken = level.block(x, y + 1, z) != block;
    if broken {
        level.set_block_notify(x, y, z, Block::AIR);
    }
    if !level.is_normal_cube(x, y - 1, z) {
        level.set_block_notify(x, y, z, Block::AIR);
        broken = true;
        if level.block(x, y + 1, z) == block {
            level.set_block_notify(x, y + 1, z, Block::AIR);
        }
    }
    if broken {
        drop_as_item(level, block, x, y, z, meta);
    } else if power_change(level, neighbor) {
        let open = level.powered_or_above(x, y, z);
        power_door(level, block, x, y, z, open);
    }
}

/// `BlockTrapDoor.onNeighborBlockChange`: drops without the block it hangs on, follows power.
pub fn trapdoor_neighbor_changed(level: &mut Level, x: i32, y: i32, z: i32, neighbor: Block) {
    let meta = level.metadata(x, y, z);
    let [dx, dz] = super::WALLS[usize::from(meta & 3)];
    if !level.is_normal_cube(x + dx, y, z + dz) {
        level.set_block_notify(x, y, z, Block::AIR);
        drop_as_item(level, Block::TRAPDOOR, x, y, z, meta);
    }
    if power_change(level, neighbor) {
        let open = level.powered(x, y, z);
        let meta = level.metadata(x, y, z);
        if (meta & 4 != 0) != open {
            level.set_metadata_notify(x, y, z, meta ^ 4);
            level.aux_effect(1003, [x, y, z], 0);
        }
    }
}

// ---- TNT ----------------------------------------------------

/// `BlockTNT`: ignites when powered, on placement or by a neighbor.
pub fn tnt_check(level: &mut Level, x: i32, y: i32, z: i32, neighbor: Option<Block>) {
    if neighbor.is_none_or(|neighbor| power_change(level, neighbor)) && level.powered(x, y, z) {
        let center = [x, y, z].map(|v| f64::from(v as f32 + 0.5));
        level.prime_tnt(DVec3::from(center), false);
        level.set_block_notify(x, y, z, Block::AIR);
    }
}

// ---- Note blocks --------------------------------------------

/// `TileEntityNote.triggerNote`: sounds the instrument of the block below when uncovered.
pub fn trigger_note(level: &mut Level, x: i32, y: i32, z: i32) {
    if level.material(x, y + 1, z) != Material::Air {
        return;
    }
    let instrument = match level.material(x, y - 1, z) {
        Material::Rock => 1,
        Material::Sand => 2,
        Material::Glass => 3,
        Material::Wood => 4,
        _ => 0,
    };
    if let Some(TileEntity::Note { note, .. }) = level.tile_entity(x, y, z) {
        let pitch = *note as u8;
        level.play_note([x, y, z], instrument, pitch);
    }
}

/// `BlockNote.blockActivated`: the next pitch.
pub fn tune_note(level: &mut Level, x: i32, y: i32, z: i32) {
    if let Some(TileEntity::Note { note, .. }) = level.tile_entity_mut(x, y, z) {
        *note = (*note + 1) % 25;
    }
    trigger_note(level, x, y, z);
}

pub fn note_neighbor_changed(level: &mut Level, x: i32, y: i32, z: i32, neighbor: Block) {
    if !power_change(level, neighbor) {
        return;
    }
    let powered = level.strongly_powered(x, y, z);
    if let Some(TileEntity::Note {
        powered: previous, ..
    }) = level.tile_entity_mut(x, y, z)
        && *previous != powered
    {
        *previous = powered;
        if powered {
            trigger_note(level, x, y, z);
        }
    }
}

// ---- Dispensers ---------------------------------------------

/// `setDefaultDirection` of furnaces and dispensers: away from a solid neighbor, unless
/// placement already chose a facing.
pub fn default_facing(level: &mut Level, x: i32, y: i32, z: i32) {
    if level.metadata(x, y, z) != 0 {
        return;
    }
    let opaque = |level: &mut Level, x, z| level.block(x, y, z).is_opaque_cube();
    let (north, south) = (opaque(level, x, z - 1), opaque(level, x, z + 1));
    let (west, east) = (opaque(level, x - 1, z), opaque(level, x + 1, z));
    let facing = match (west, east) {
        (true, false) => 5,
        (false, true) => 4,
        _ if south && !north => 2,
        _ => 3,
    };
    level.set_metadata_notify(x, y, z, facing);
}

pub fn dispenser_neighbor_changed(level: &mut Level, x: i32, y: i32, z: i32, neighbor: Block) {
    if power_change(level, neighbor) && level.powered_or_above(x, y, z) {
        level.schedule_tick(x, y, z, Block::DISPENSER, 4);
    }
}

/// `BlockDispenser.updateTick` and `dispenseItem`: shoots arrows, snowballs and eggs, drops
/// anything else.
pub fn dispense(level: &mut Level, x: i32, y: i32, z: i32) {
    if !level.powered_or_above(x, y, z) {
        return;
    }
    let (dx, dz) = match level.metadata(x, y, z) {
        3 => (0, 1),
        2 => (0, -1),
        5 => (1, 0),
        _ => (-1, 0),
    };
    let Some(TileEntity::Dispenser(items)) = level.tile_entity(x, y, z) else {
        return;
    };
    let filled: Vec<usize> = (0..items.len())
        .filter(|&slot| items[slot].is_some())
        .collect();
    let mut chosen = None;
    for (index, slot) in filled.into_iter().enumerate() {
        if level.block_rand.next_int(index as i32 + 1) == 0 {
            chosen = Some(slot);
        }
    }
    let stack = match (chosen, level.tile_entity_mut(x, y, z)) {
        (Some(slot), Some(TileEntity::Dispenser(items))) => {
            common::inventory::take(&mut items[slot], 1)
        }
        _ => None,
    };
    let position = DVec3::new(
        f64::from(x) + f64::from(dx) * 0.6 + 0.5,
        f64::from(y) + 0.5,
        f64::from(z) + f64::from(dz) * 0.6 + 0.5,
    );
    let Some(stack) = stack else {
        level.aux_effect(1001, [x, y, z], 0);
        return;
    };
    let direction = DVec3::new(f64::from(dx), f64::from(0.1_f32), f64::from(dz));
    match stack.item {
        Item::ARROW => {
            level.dispense_arrow(position, direction);
            level.aux_effect(1002, [x, y, z], 0);
        }
        Item::EGG | Item::SNOWBALL => {
            level.throw(stack.item == Item::EGG, position, direction, 1.1, 6.0, None);
            level.aux_effect(1002, [x, y, z], 0);
        }
        _ => {
            let random = &mut level.block_rand;
            let speed = random.next_double() * 0.1 + 0.2;
            let mut motion = DVec3::new(
                f64::from(dx) * speed,
                f64::from(0.2_f32),
                f64::from(dz) * speed,
            );
            for axis in 0..3 {
                motion[axis] += random.next_gaussian() * f64::from(0.0075_f32) * 6.0;
            }
            let pos = [position.x, position.y - 0.3, position.z];
            super::spawn_item(level, pos, stack, Some(motion));
            level.aux_effect(1000, [x, y, z], 0);
        }
    }
    level.aux_effect(2000, [x, y, z], dx + 1 + (dz + 1) * 3);
}

// ---- Jukeboxes ----------------------------------------------

/// `ItemRecord.onItemUse`: puts a record into an empty jukebox and starts it.
pub fn insert_record(level: &mut Level, x: i32, y: i32, z: i32, record: Item) -> bool {
    if level.block(x, y, z) != Block::JUKEBOX || level.metadata(x, y, z) != 0 {
        return false;
    }
    if let Some(TileEntity::Jukebox(stored)) = level.tile_entity_mut(x, y, z) {
        *stored = i32::from(record.0);
    }
    level.set_metadata_notify(x, y, z, 1);
    level.aux_effect(1005, [x, y, z], i32::from(record.0));
    true
}

/// `BlockJukeBox.ejectRecord`: stops the record and throws it out of the top.
pub fn eject_record(level: &mut Level, x: i32, y: i32, z: i32) {
    let Some(TileEntity::Jukebox(stored)) = level.tile_entity_mut(x, y, z) else {
        return;
    };
    let record = std::mem::take(stored);
    if record == 0 {
        return;
    }
    level.aux_effect(1005, [x, y, z], 0);
    if level.block(x, y, z) == Block::JUKEBOX {
        level.set_metadata_notify(x, y, z, 0);
    }
    let spread = 0.7_f32;
    let mut offset =
        |low: f64| f64::from(level.rand.next_float() * spread) + f64::from(1.0 - spread) * low;
    let (dx, dy, dz) = (offset(0.5), offset(0.2) + 0.6, offset(0.5));
    let pos = [f64::from(x) + dx, f64::from(y) + dy, f64::from(z) + dz];
    super::spawn_item(level, pos, ItemStack::one(Item(record as i16)), None);
}