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