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

//! Redstone: the power queries of `World` with the power methods of the blocks that provide
//! it, and `BlockRedstoneWire`, `BlockRedstoneTorch`, `BlockRedstoneRepeater`, `BlockLever`,
//! `BlockButton` and `BlockPressurePlate`.

use common::entity::Entity;
use common::physics::Aabb;
use common::protocol::Packet;
use common::world::Block;
use common::world::shape::{BlockView, SIDE_OFFSETS};
use rustc_hash::FxHashSet;

use super::{SIDES, break_block};
use crate::level::Level;

/// `BlockRedstoneWire.blocksNeedingUpdate`: a Java 8 `HashSet<ChunkPosition>`, whose
/// iteration order decides the order of the wire's neighbor updates.
#[derive(Default)]
pub struct UpdateSet {
    entries: Vec<[i32; 3]>,
    members: FxHashSet<[i32; 3]>,
    /// The table size, which never shrinks when the set is cleared.
    capacity: usize,
}

impl UpdateSet {
    fn insert(&mut self, position: [i32; 3]) {
        if self.members.insert(position) {
            self.entries.push(position);
            self.capacity = self.capacity.max(16);
            while self.entries.len() > self.capacity * 3 / 4 {
                self.capacity *= 2;
            }
        }
    }

    /// The entries in iteration order, leaving the set empty.
    fn drain(&mut self) -> Vec<[i32; 3]> {
        let mask = self.capacity.max(16) as i32 - 1;
        let bucket = |&[x, y, z]: &[i32; 3]| {
            let hash = x
                .wrapping_mul(8_976_890)
                .wrapping_add(y.wrapping_mul(981_131))
                .wrapping_add(z);
            (hash ^ ((hash as u32) >> 16) as i32) & mask
        };
        let mut entries = std::mem::take(&mut self.entries);
        entries.sort_by_key(bucket);
        self.members.clear();
        entries
    }
}

/// `BlockRedstoneTorch.torchUpdates`: recent switches, for burning out torches in fast clocks.
#[derive(Default)]
pub struct TorchUpdates(Vec<([i32; 3], i64)>);

fn view(level: &Level, x: i32, y: i32, z: i32) -> (Block, u8) {
    (
        BlockView::block(level, x, y, z),
        BlockView::metadata(level, x, y, z),
    )
}

/// `Block.canProvidePower`.
pub fn can_provide_power(level: &Level, block: Block) -> bool {
    match block {
        Block::REDSTONE_WIRE => level.wires_provide_power,
        Block::REDSTONE_TORCH
        | Block::UNLIT_REDSTONE_TORCH
        | Block::LEVER
        | Block::BUTTON
        | Block::STONE_PRESSURE_PLATE
        | Block::WOODEN_PRESSURE_PLATE
        | Block::DETECTOR_RAIL => true,
        _ => false,
    }
}

/// `Block.isPoweringTo`: whether the block powers its neighbor on `side`.
fn powering(level: &Level, x: i32, y: i32, z: i32, side: u8) -> bool {
    let (block, meta) = view(level, x, y, z);
    match block {
        Block::REDSTONE_WIRE => wire_powering(level, x, y, z, side),
        Block::REDSTONE_TORCH => {
            !matches!((meta, side), (5, 1) | (3, 3) | (4, 2) | (1, 5) | (2, 4))
        }
        Block::POWERED_REPEATER => matches!((meta & 3, side), (0, 3) | (1, 4) | (2, 2) | (3, 5)),
        Block::LEVER | Block::BUTTON | Block::DETECTOR_RAIL => meta & 8 != 0,
        Block::STONE_PRESSURE_PLATE | Block::WOODEN_PRESSURE_PLATE => meta > 0,
        _ => false,
    }
}

/// `Block.isIndirectlyPoweringTo`: whether the block powers the block on `side` through it.
fn strongly_powering(level: &Level, x: i32, y: i32, z: i32, side: u8) -> bool {
    let (block, meta) = view(level, x, y, z);
    match block {
        Block::REDSTONE_WIRE => level.wires_provide_power && powering(level, x, y, z, side),
        Block::REDSTONE_TORCH => side == 0 && powering(level, x, y, z, side),
        Block::POWERED_REPEATER => powering(level, x, y, z, side),
        Block::LEVER | Block::BUTTON => {
            meta & 8 != 0
                && (matches!((meta & 7, side), (5, 1) | (4, 2) | (3, 3) | (2, 4) | (1, 5))
                    || block == Block::LEVER && (meta & 7, side) == (6, 1))
        }
        Block::STONE_PRESSURE_PLATE | Block::WOODEN_PRESSURE_PLATE => meta != 0 && side == 1,
        Block::DETECTOR_RAIL => meta & 8 != 0 && side == 1,
        _ => false,
    }
}

impl Level {
    /// `World.isBlockProvidingPowerTo`.
    pub fn strong_power_from(&self, x: i32, y: i32, z: i32, side: u8) -> bool {
        strongly_powering(self, x, y, z, side)
    }

    /// `World.isBlockGettingPowered`: strongly powered by a neighbor.
    pub fn strongly_powered(&self, x: i32, y: i32, z: i32) -> bool {
        SIDE_OFFSETS
            .iter()
            .enumerate()
            .any(|(side, [dx, dy, dz])| self.strong_power_from(x + dx, y + dy, z + dz, side as u8))
    }

    /// `World.isBlockIndirectlyProvidingPowerTo`: solid blocks pass on strong power.
    pub fn power_from(&self, x: i32, y: i32, z: i32, side: u8) -> bool {
        if BlockView::block(self, x, y, z).is_normal_cube() {
            self.strongly_powered(x, y, z)
        } else {
            powering(self, x, y, z, side)
        }
    }

    /// `World.isBlockIndirectlyGettingPowered`.
    pub fn powered(&self, x: i32, y: i32, z: i32) -> bool {
        SIDE_OFFSETS
            .iter()
            .enumerate()
            .any(|(side, [dx, dy, dz])| self.power_from(x + dx, y + dy, z + dz, side as u8))
    }

    /// Powered from any side or from above (`isBlockIndirectlyGettingPowered` of the block and
    /// the one on top), as doors, dispensers and powered rails check.
    pub fn powered_or_above(&self, x: i32, y: i32, z: i32) -> bool {
        self.powered(x, y, z) || self.powered(x, y + 1, z)
    }

    fn click(&mut self, x: i32, y: f64, z: i32, pitch: f32) {
        self.events.push(Packet::Sound {
            name: "random.click".into(),
            pos: [f64::from(x) + 0.5, y, f64::from(z) + 0.5],
            volume: 0.3,
            pitch,
        });
    }
}

// ---- Wire ---------------------------------------------------

pub fn wire_added(level: &mut Level, x: i32, y: i32, z: i32) {
    update_wire(level, x, y, z);
    level.notify_neighbors(x, y + 1, z, Block::REDSTONE_WIRE);
    level.notify_neighbors(x, y - 1, z, Block::REDSTONE_WIRE);
    notify_wire_surroundings(level, x, y, z);
}

pub fn wire_removed(level: &mut Level, x: i32, y: i32, z: i32) {
    level.notify_neighbors(x, y + 1, z, Block::REDSTONE_WIRE);
    level.notify_neighbors(x, y - 1, z, Block::REDSTONE_WIRE);
    update_wire(level, x, y, z);
    notify_wire_surroundings(level, x, y, z);
}

pub fn wire_neighbor_changed(level: &mut Level, x: i32, y: i32, z: i32) {
    if level.is_normal_cube(x, y - 1, z) {
        update_wire(level, x, y, z);
    } else {
        break_block(level, Block::REDSTONE_WIRE, x, y, z);
    }
}

/// The wire neighbors of `onBlockAdded` and `onBlockRemoval`: beside, then up or down the
/// blocks beside.
fn notify_wire_surroundings(level: &mut Level, x: i32, y: i32, z: i32) {
    for [dx, dz] in SIDES {
        notify_wire(level, x + dx, y, z + dz);
    }
    for [dx, dz] in SIDES {
        let dy = if level.is_normal_cube(x + dx, y, z + dz) {
            1
        } else {
            -1
        };
        notify_wire(level, x + dx, y + dy, z + dz);
    }
}

/// `notifyWireNeighborsOfNeighborChange`.
fn notify_wire(level: &mut Level, x: i32, y: i32, z: i32) {
    if level.block(x, y, z) == Block::REDSTONE_WIRE {
        for (dx, dy, dz) in [
            (0, 0, 0),
            (-1, 0, 0),
            (1, 0, 0),
            (0, 0, -1),
            (0, 0, 1),
            (0, -1, 0),
            (0, 1, 0),
        ] {
            level.notify_neighbors(x + dx, y + dy, z + dz, Block::REDSTONE_WIRE);
        }
    }
}

/// `updateAndPropagateCurrentStrength`.
fn update_wire(level: &mut Level, x: i32, y: i32, z: i32) {
    calculate_wire(level, [x, y, z], [x, y, z]);
    for [x, y, z] in level.wire_updates.drain() {
        level.notify_neighbors(x, y, z, Block::REDSTONE_WIRE);
    }
}

/// `getMaxCurrentStrength`.
fn max_strength(level: &mut Level, x: i32, y: i32, z: i32, power: i32) -> i32 {
    if level.block(x, y, z) == Block::REDSTONE_WIRE {
        power.max(i32::from(level.metadata(x, y, z)))
    } else {
        power
    }
}

/// `calculateCurrentChanges`: the strength from the neighbors except the one at `source`.
fn calculate_wire(level: &mut Level, [x, y, z]: [i32; 3], source: [i32; 3]) {
    let old = i32::from(level.metadata(x, y, z));
    level.wires_provide_power = false;
    let powered = level.powered(x, y, z);
    level.wires_provide_power = true;
    let mut strength = 0;
    if powered {
        strength = 15;
    } else {
        for [dx, dz] in SIDES {
            let (nx, nz) = (x + dx, z + dz);
            if [nx, y, nz] != source {
                strength = max_strength(level, nx, y, nz, strength);
            }
            if level.is_normal_cube(nx, y, nz) && !level.is_normal_cube(x, y + 1, z) {
                if [nx, y + 1, nz] != source {
                    strength = max_strength(level, nx, y + 1, nz, strength);
                }
            } else if !level.is_normal_cube(nx, y, nz) && [nx, y - 1, nz] != source {
                strength = max_strength(level, nx, y - 1, nz, strength);
            }
        }
        strength = (strength - 1).max(0);
    }
    if old == strength {
        return;
    }
    level.editing_blocks = true;
    level.set_metadata_notify(x, y, z, strength as u8);
    level.editing_blocks = false;
    let mut current = strength;
    for [dx, dz] in SIDES {
        let (nx, nz) = (x + dx, z + dz);
        let ny = if level.is_normal_cube(nx, y, nz) {
            y + 1
        } else {
            y - 1
        };
        for target in [y, ny] {
            let neighbor = max_strength(level, nx, target, nz, -1);
            current = (i32::from(level.metadata(x, y, z)) - 1).max(0);
            if neighbor >= 0 && neighbor != current {
                calculate_wire(level, [nx, target, nz], [x, y, z]);
            }
        }
    }
    if old == 0 || current == 0 {
        level.wire_updates.insert([x, y, z]);
        for (dx, dy, dz) in crate::level::NEIGHBORS {
            level.wire_updates.insert([x + dx, y + dy, z + dz]);
        }
    }
}

/// `BlockRedstoneWire.isPoweringTo`: wire powers what it points into, and down.
fn wire_powering(level: &Level, x: i32, y: i32, z: i32, side: u8) -> bool {
    if !level.wires_provide_power || BlockView::metadata(level, x, y, z) == 0 {
        return false;
    }
    if side == 1 {
        return true;
    }
    let [west, east, north, south] = common::world::redstone::links(level, x, y, z);
    match side {
        2..=5 if !north && !east && !west && !south => true,
        2 => north && !west && !east,
        3 => south && !west && !east,
        4 => west && !north && !south,
        5 => east && !north && !south,
        _ => false,
    }
}

// ---- Torches ------------------------------------------------

const AROUND: [(i32, i32, i32); 6] = [
    (0, -1, 0),
    (0, 1, 0),
    (-1, 0, 0),
    (1, 0, 0),
    (0, 0, -1),
    (0, 0, 1),
];

/// The neighbor updates of a lit torch being added or removed.
pub fn torch_changed(level: &mut Level, block: Block, x: i32, y: i32, z: i32) {
    if block == Block::REDSTONE_TORCH {
        for (dx, dy, dz) in AROUND {
            level.notify_neighbors(x + dx, y + dy, z + dz, block);
        }
    }
}

/// `BlockRedstoneTorch.isIndirectlyPowered`: whether the block it is attached to is powered.
fn torch_powered(level: &Level, x: i32, y: i32, z: i32) -> bool {
    match BlockView::metadata(level, x, y, z) {
        5 => level.power_from(x, y - 1, z, 0),
        3 => level.power_from(x, y, z - 1, 2),
        4 => level.power_from(x, y, z + 1, 3),
        1 => level.power_from(x - 1, y, z, 4),
        2 => level.power_from(x + 1, y, z, 5),
        _ => false,
    }
}

impl TorchUpdates {
    /// `checkForBurnout`: whether the torch switched eight times recently.
    fn burnt_out(&mut self, position: [i32; 3], time: i64, add: bool) -> bool {
        if add {
            self.0.push((position, time));
        }
        self.0.iter().filter(|(p, _)| *p == position).count() >= 8
    }
}

/// `BlockRedstoneTorch.updateTick`: turns off when its support is powered, and back on.
pub fn torch_tick(level: &mut Level, block: Block, x: i32, y: i32, z: i32) {
    let powered = torch_powered(level, x, y, z);
    let time = level.time;
    let updates = &mut level.torch_updates.0;
    let expired = updates.iter().take_while(|(_, t)| time - t > 100).count();
    updates.drain(..expired);
    let meta = level.metadata(x, y, z);
    if block == Block::REDSTONE_TORCH {
        if powered {
            level.set_block_with_metadata_notify(x, y, z, Block::UNLIT_REDSTONE_TORCH, meta);
            if level.torch_updates.burnt_out([x, y, z], time, true) {
                super::fluid::fizz(level, [x, y, z]);
                for _ in 0..5 {
                    let pos =
                        [x, y, z].map(|v| f64::from(v) + (level.rand.next_double() * 0.6 + 0.2));
                    level.events.push(Packet::Particle {
                        name: "smoke".into(),
                        pos,
                        velocity: [0.0; 3],
                    });
                }
            }
        }
    } else if !powered && !level.torch_updates.burnt_out([x, y, z], time, false) {
        level.set_block_with_metadata_notify(x, y, z, Block::REDSTONE_TORCH, meta);
    }
}

// ---- Repeaters ----------------------------------------------

/// `BlockRedstoneRepeater.ignoreTick`: whether the input behind the repeater is powered.
fn repeater_input(level: &Level, x: i32, y: i32, z: i32, meta: u8) -> bool {
    let (dx, dz, side) = match meta & 3 {
        0 => (0, 1, 3),
        1 => (-1, 0, 4),
        2 => (0, -1, 2),
        _ => (1, 0, 5),
    };
    let (nx, nz) = (x + dx, z + dz);
    level.power_from(nx, y, nz, side)
        || BlockView::block(level, nx, y, nz) == Block::REDSTONE_WIRE
            && BlockView::metadata(level, nx, y, nz) > 0
}

fn repeater_delay(meta: u8) -> i64 {
    i64::from((meta & 12) >> 2) * 2 + 2
}

pub fn repeater_added(level: &mut Level, block: Block, x: i32, y: i32, z: i32) {
    for (dx, dy, dz) in [
        (1, 0, 0),
        (-1, 0, 0),
        (0, 0, 1),
        (0, 0, -1),
        (0, -1, 0),
        (0, 1, 0),
    ] {
        level.notify_neighbors(x + dx, y + dy, z + dz, block);
    }
}

/// `onBlockPlacedBy`: an input powered from the start switches the repeater on.
pub fn repeater_placed(level: &mut Level, x: i32, y: i32, z: i32) {
    let meta = level.metadata(x, y, z);
    if repeater_input(level, x, y, z, meta) {
        level.schedule_tick(x, y, z, Block::REPEATER, 1);
    }
}

pub fn repeater_neighbor_changed(level: &mut Level, block: Block, x: i32, y: i32, z: i32) {
    if !level.is_normal_cube(x, y - 1, z) {
        break_block(level, block, x, y, z);
        return;
    }
    let meta = level.metadata(x, y, z);
    let input = repeater_input(level, x, y, z, meta);
    if (block == Block::POWERED_REPEATER) != input {
        level.schedule_tick(x, y, z, block, repeater_delay(meta));
    }
}

pub fn repeater_tick(level: &mut Level, block: Block, x: i32, y: i32, z: i32) {
    let meta = level.metadata(x, y, z);
    let input = repeater_input(level, x, y, z, meta);
    if block == Block::POWERED_REPEATER {
        if !input {
            level.set_block_with_metadata_notify(x, y, z, Block::REPEATER, meta);
        }
    } else {
        level.set_block_with_metadata_notify(x, y, z, Block::POWERED_REPEATER, meta);
        if !input {
            level.schedule_tick(x, y, z, Block::POWERED_REPEATER, repeater_delay(meta));
        }
    }
}

/// `BlockRedstoneRepeater.blockActivated`: cycles the delay.
pub fn cycle_repeater(level: &mut Level, x: i32, y: i32, z: i32) {
    let meta = level.metadata(x, y, z);
    let delay = (((meta & 12) >> 2) + 1) << 2 & 12;
    level.set_metadata_notify(x, y, z, delay | meta & 3);
}

// ---- Levers and buttons -------------------------------------

/// Where a lever or button with this orientation is attached, relative to it.
fn attachment(orientation: u8) -> (i32, i32, i32) {
    match orientation {
        1 => (-1, 0, 0),
        2 => (1, 0, 0),
        3 => (0, 0, -1),
        4 => (0, 0, 1),
        _ => (0, -1, 0),
    }
}

/// The updates of a switch changing state: around itself and around its support.
fn switch_changed(level: &mut Level, block: Block, x: i32, y: i32, z: i32, orientation: u8) {
    level.notify_neighbors(x, y, z, block);
    let (dx, dy, dz) = attachment(orientation);
    level.notify_neighbors(x + dx, y + dy, z + dz, block);
}

/// `onBlockRemoval` of levers and buttons: an active switch turns its outputs off.
pub fn switch_removed(level: &mut Level, block: Block, x: i32, y: i32, z: i32) {
    let meta = level.metadata(x, y, z);
    if meta & 8 != 0 {
        switch_changed(level, block, x, y, z, meta & 7);
    }
}

/// `canPlaceBlockOnSide` and `onBlockPlaced` of levers and buttons: the orientation for the
/// clicked side, or `None` when that side cannot hold it.
pub fn switch_orientation(
    level: &mut Level,
    block: Block,
    [x, y, z]: [i32; 3],
    side: u8,
) -> Option<u8> {
    match side {
        1 if block == Block::LEVER && level.is_normal_cube(x, y - 1, z) => {
            Some(5 + level.rand.next_int(2) as u8)
        }
        2 if level.is_normal_cube(x, y, z + 1) => Some(4),
        3 if level.is_normal_cube(x, y, z - 1) => Some(3),
        4 if level.is_normal_cube(x + 1, y, z) => Some(2),
        5 if level.is_normal_cube(x - 1, y, z) => Some(1),
        _ => None,
    }
}

/// `onNeighborBlockChange` of levers and buttons: they drop without any support, or without
/// the one they are attached to.
pub fn switch_neighbor_changed(level: &mut Level, block: Block, x: i32, y: i32, z: i32) {
    let lever = block == Block::LEVER;
    let supports = [(-1, 0, 0), (1, 0, 0), (0, 0, -1), (0, 0, 1), (0, -1, 0)];
    let attached = supports[..if lever { 5 } else { 4 }]
        .iter()
        .any(|&(dx, dy, dz)| level.is_normal_cube(x + dx, y + dy, z + dz));
    let orientation = level.metadata(x, y, z) & 7;
    let loose = attached && (1..=if lever { 6 } else { 4 }).contains(&orientation) && {
        let (dx, dy, dz) = attachment(orientation);
        !level.is_normal_cube(x + dx, y + dy, z + dz)
    };
    if !attached || loose {
        break_block(level, block, x, y, z);
    }
}

/// `BlockLever.blockActivated`.
pub fn flip_lever(level: &mut Level, x: i32, y: i32, z: i32) {
    let meta = level.metadata(x, y, z);
    let on = meta & 8 == 0;
    level.set_metadata_notify(x, y, z, meta & 7 | if on { 8 } else { 0 });
    level.click(x, f64::from(y) + 0.5, z, if on { 0.6 } else { 0.5 });
    switch_changed(level, Block::LEVER, x, y, z, meta & 7);
}

/// `BlockButton.blockActivated`: pressed for a second.
pub fn press_button(level: &mut Level, x: i32, y: i32, z: i32) {
    let meta = level.metadata(x, y, z);
    if meta & 8 != 0 {
        return;
    }
    level.set_metadata_notify(x, y, z, meta | 8);
    level.click(x, f64::from(y) + 0.5, z, 0.6);
    switch_changed(level, Block::BUTTON, x, y, z, meta & 7);
    level.schedule_tick(x, y, z, Block::BUTTON, 20);
}

/// `BlockButton.updateTick`: the release.
pub fn release_button(level: &mut Level, x: i32, y: i32, z: i32) {
    let meta = level.metadata(x, y, z);
    if meta & 8 != 0 {
        level.set_metadata_notify(x, y, z, meta & 7);
        switch_changed(level, Block::BUTTON, x, y, z, meta & 7);
        level.click(x, f64::from(y) + 0.5, z, 0.5);
    }
}

// ---- Pressure plates ----------------------------------------

/// The trigger area of pressure plates and detector rails.
pub fn plate_area(x: i32, y: i32, z: i32) -> Aabb {
    let inset = 2.0_f32 / 16.0;
    Aabb::from_coords(
        f64::from(x as f32 + inset),
        f64::from(y),
        f64::from(z as f32 + inset),
        f64::from((x + 1) as f32 - inset),
        f64::from(y) + 0.25,
        f64::from((z + 1) as f32 - inset),
    )
}

/// Whether a pressure plate of this kind reacts to the entity.
fn triggers(block: Block, entity: &Entity) -> bool {
    block == Block::WOODEN_PRESSURE_PLATE || entity.living.is_some()
}

/// `setStateIfMobInteractsWithPlate`; `extra` is an entity outside the level being moved.
pub fn update_plate(
    level: &mut Level,
    block: Block,
    x: i32,
    y: i32,
    z: i32,
    extra: Option<&Entity>,
) {
    let pressed = level.metadata(x, y, z) == 1;
    let area = plate_area(x, y, z);
    let touching = extra.is_some_and(|e| triggers(block, e) && e.bb.intersects(&area))
        || level
            .entities
            .iter()
            .any(|e| !e.entity.dead && triggers(block, &e.entity) && e.entity.bb.intersects(&area));
    if touching != pressed {
        level.set_metadata_notify(x, y, z, u8::from(touching));
        level.notify_neighbors(x, y, z, block);
        level.notify_neighbors(x, y - 1, z, block);
        level.click(x, f64::from(y) + 0.1, z, if touching { 0.6 } else { 0.5 });
    }
    if touching {
        level.schedule_tick(x, y, z, block, 20);
    }
}

pub fn plate_removed(level: &mut Level, block: Block, x: i32, y: i32, z: i32) {
    if level.metadata(x, y, z) > 0 {
        level.notify_neighbors(x, y, z, block);
        level.notify_neighbors(x, y - 1, z, block);
    }
}