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 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);
}
}