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 piston.rs
//! Pistons: `BlockPistonBase`, `BlockPistonExtension`, `BlockPistonMoving` and `TileEntityPiston`.
use common::protocol::Packet;
use common::world::shape::{self, SIDE_OFFSETS};
use common::world::{Block, Material, TileEntity};
use glam::DVec3;
use super::break_block;
use crate::level::Level;
const OPPOSITE: [u8; 6] = [1, 0, 3, 2, 5, 4];
/// The facing, extending and source flags of a moving block.
type Motion = (u8, bool, bool);
fn step([x, y, z]: [i32; 3], facing: u8, distance: i32) -> [i32; 3] {
let [dx, dy, dz] = SIDE_OFFSETS[usize::from(facing)];
[x + dx * distance, y + dy * distance, z + dz * distance]
}
fn is_piston(block: Block) -> bool {
matches!(block, Block::PISTON | Block::STICKY_PISTON)
}
/// `Block.getMobilityFlag`: 0 pushable, 1 broken when pushed, 2 immovable.
pub fn mobility(block: Block) -> u8 {
match block {
Block::BED | Block::WOODEN_DOOR | Block::IRON_DOOR => 1,
Block::STONE_PRESSURE_PLATE | Block::WOODEN_PRESSURE_PLATE => 1,
Block::ICE | Block::RAIL | Block::POWERED_RAIL | Block::DETECTOR_RAIL => 0,
_ => match block.material() {
Material::Water
| Material::Lava
| Material::Leaves
| Material::Plants
| Material::Fire
| Material::Circuits
| Material::Snow
| Material::Cactus
| Material::Pumpkin
| Material::Cake
| Material::Web => 1,
Material::Portal | Material::Piston => 2,
_ => 0,
},
}
}
/// `BlockPistonBase.canPushBlock`.
fn can_push(level: &mut Level, block: Block, [x, y, z]: [i32; 3], breaking: bool) -> bool {
if block == Block::OBSIDIAN {
return false;
}
let movable = if is_piston(block) {
level.metadata(x, y, z) & 8 == 0
} else {
block.hardness() != -1.0 && mobility(block) != 2 && (breaking || mobility(block) != 1)
};
movable && !block.is_container()
}
/// The end of the line of blocks a piston pushes, and whether it can be pushed at all
/// (`canExtend`); with `breaking` the block that gets in the way is destroyed (`tryExtend`).
fn push_end(level: &mut Level, piston: [i32; 3], facing: u8, breaking: bool) -> Option<[i32; 3]> {
let mut position = step(piston, facing, 1);
for distance in 0..13 {
let [x, y, z] = position;
if y <= 0 || y >= 127 {
return None;
}
let block = level.block(x, y, z);
if block.is_air() {
break;
}
if !can_push(level, block, position, true) {
return None;
}
if mobility(block) == 1 {
if breaking {
break_block(level, block, x, y, z);
}
break;
}
if distance == 12 {
return None;
}
position = step(position, facing, 1);
}
Some(position)
}
/// `determineOrientation`: towards the placer, vertically when close by.
pub fn orientation(placer: DVec3, yaw: f32, [x, y, z]: [i32; 3]) -> u8 {
if (placer.x as f32 - x as f32).abs() < 2.0 && (placer.z as f32 - z as f32).abs() < 2.0 {
let eye = placer.y + 1.82;
if eye - f64::from(y) > 2.0 {
return 1;
}
if f64::from(y) - eye > 0.0 {
return 0;
}
}
[2, 5, 3, 4][super::placement::facing(yaw)]
}
/// `isPowered`: from any side but the front, or from around the block above.
fn powered(level: &Level, position @ [x, y, z]: [i32; 3], facing: u8) -> bool {
(0..6).filter(|&side| side != facing).any(|side| {
let [nx, ny, nz] = step(position, side, 1);
level.power_from(nx, ny, nz, side)
}) || level.powered(x, y + 1, z)
}
/// `updatePistonState`: starts extending or retracting when the power changed.
pub fn update(level: &mut Level, block: Block, x: i32, y: i32, z: i32) {
if level.piston_ignore[usize::from(block == Block::STICKY_PISTON)] {
return;
}
let meta = level.metadata(x, y, z);
let facing = meta & 7;
if meta == 7 || facing > 5 {
return;
}
let power = powered(level, [x, y, z], facing);
let extended = meta & 8 != 0;
if power && !extended {
if push_end(level, [x, y, z], facing, false).is_some() {
level.set_metadata(x, y, z, facing | 8);
level.play_note([x, y, z], 0, facing);
}
} else if !power && extended {
level.set_metadata(x, y, z, facing);
level.play_note([x, y, z], 1, facing);
}
}
/// `onBlockAdded`: a piston that is not the end of a move checks its power.
pub fn added(level: &mut Level, block: Block, x: i32, y: i32, z: i32) {
if level.tile_entity(x, y, z).is_none() {
update(level, block, x, y, z);
}
}
fn sound(level: &mut Level, [x, y, z]: [i32; 3], name: &str, pitch: f32) {
level.events.push(Packet::Sound {
name: name.into(),
pos: [x, y, z].map(|v| f64::from(v) + 0.5),
volume: 0.5,
pitch,
});
}
/// Puts a moving block at `position` (`setBlockAndMetadata` with its tile entity).
/// Clients draw the moving block from its tile entity.
fn set_moving(level: &mut Level, [x, y, z]: [i32; 3], block: Block, metadata: u8, motion: Motion) {
let (facing, extending, source) = motion;
let tile = TileEntity::piston(block, metadata, facing, extending, source);
level.set_block_with_metadata(x, y, z, Block::MOVING_PISTON, metadata);
level.events.push(Packet::TileEntity {
x,
y,
z,
data: Box::new(tile.clone()),
});
level.set_tile_entity(x, y, z, tile);
}
/// `BlockPistonBase.playBlock`: the extension (0) or retraction (1) itself.
pub fn play(level: &mut Level, piston: [i32; 3], action: u8, facing: u8) {
if facing > 5 {
return;
}
let [x, y, z] = piston;
let block = level.block(x, y, z);
let sticky = block == Block::STICKY_PISTON;
let ignore = usize::from(sticky);
level.piston_ignore[ignore] = true;
if action == 0 {
if extend(level, piston, facing, sticky) {
level.set_metadata_notify(x, y, z, facing | 8);
let pitch = level.rand.next_float() * 0.25 + 0.6;
sound(level, piston, "tile.piston.out", pitch);
}
} else if action == 1 {
let head = step(piston, facing, 1);
clear(level, head);
set_moving(level, piston, block, facing, (facing, false, true));
let mut remove_head = true;
if sticky {
let pulled @ [px, py, pz] = step(piston, facing, 2);
let mut target = level.block(px, py, pz);
let mut meta = level.metadata(px, py, pz);
let mut finished = false;
if target == Block::MOVING_PISTON
&& let Some(&TileEntity::Piston {
block: stored,
metadata,
facing: moving,
extending: true,
..
}) = level.tile_entity(px, py, pz)
&& moving == i32::from(facing)
{
clear(level, pulled);
target = Block(stored as u8);
meta = metadata as u8;
finished = true;
}
let pullable = !finished
&& !target.is_air()
&& can_push(level, target, pulled, false)
&& (mobility(target) == 0 || is_piston(target));
if pullable {
level.piston_ignore[ignore] = false;
level.set_block_notify(px, py, pz, Block::AIR);
level.piston_ignore[ignore] = true;
set_moving(level, head, target, meta, (facing, false, false));
}
remove_head = !pullable && !finished;
}
if remove_head {
let [hx, hy, hz] = head;
level.piston_ignore[ignore] = false;
level.set_block_notify(hx, hy, hz, Block::AIR);
level.piston_ignore[ignore] = true;
}
let pitch = level.rand.next_float() * 0.15 + 0.6;
sound(level, piston, "tile.piston.in", pitch);
}
level.piston_ignore[ignore] = false;
}
/// `tryExtend`: shifts the line of blocks in front one step, as moving blocks.
fn extend(level: &mut Level, piston: [i32; 3], facing: u8, sticky: bool) -> bool {
let Some(mut position) = push_end(level, piston, facing, true) else {
return false;
};
while position != piston {
let previous @ [px, py, pz] = step(position, facing, -1);
let mut block = level.block(px, py, pz);
let mut meta = level.metadata(px, py, pz);
if previous == piston && is_piston(block) {
(block, meta) = (Block::PISTON_HEAD, facing | if sticky { 8 } else { 0 });
}
set_moving(level, position, block, meta, (facing, true, false));
position = previous;
}
true
}
/// `TileEntityPiston.clearPistonTileEntity`: finishes a move at once.
pub fn clear(level: &mut Level, [x, y, z]: [i32; 3]) {
let Some(&TileEntity::Piston {
block,
metadata,
last_progress,
..
}) = level.tile_entity(x, y, z)
else {
return;
};
if last_progress < 1.0 {
level.remove_tile_entity(x, y, z);
if level.block(x, y, z) == Block::MOVING_PISTON {
level.set_block_with_metadata_notify(x, y, z, Block(block as u8), metadata as u8);
}
}
}
/// `TileEntityPiston.updateEntity`: half a block per tick, pushing entities along.
pub fn tick(level: &mut Level, [x, y, z]: [i32; 3]) {
let Some(TileEntity::Piston {
block,
metadata,
facing,
progress,
last_progress,
extending,
..
}) = level.chunk_tile_entity_mut(x, y, z)
else {
return;
};
*last_progress = *progress;
let (block, metadata, facing, extending) = (
Block(*block as u8),
*metadata as u8,
*facing as u8,
*extending,
);
if *last_progress >= 1.0 {
push_entities(level, [x, y, z], block, facing, 0.0, 0.25);
level.remove_tile_entity(x, y, z);
if level.block(x, y, z) == Block::MOVING_PISTON {
level.set_block_with_metadata_notify(x, y, z, block, metadata);
}
} else {
let previous = *last_progress;
*progress = (*progress + 0.5).min(1.0);
let current = *progress;
if extending {
let distance = current - previous + 1.0 / 16.0;
push_entities(level, [x, y, z], block, facing, 1.0 - current, distance);
}
}
}
/// `updatePushedObjects`: moves the entities in the way of the moving block, which is
/// `remaining` blocks short of its destination.
fn push_entities(
level: &mut Level,
[x, y, z]: [i32; 3],
block: Block,
facing: u8,
remaining: f32,
distance: f32,
) {
let Some(area) = shape::moving_collision_box(&*level, block, x, y, z, facing, remaining) else {
return;
};
let ids: Vec<i32> = level
.entities
.iter()
.filter(|e| e.entity.bb.intersects(&area))
.map(|e| e.entity.id)
.collect();
let offset = SIDE_OFFSETS[usize::from(facing)];
let motion = DVec3::from(offset.map(|d| f64::from(distance * d as f32)));
for id in ids {
if let Some(mut stored) = level.entities.take_for_tick(id) {
// `EntityPainting.moveEntity`: any push breaks it
if matches!(stored.kind, crate::entity::Kind::Painting { .. }) {
if !stored.entity.dead && motion != DVec3::ZERO {
stored.entity.dead = true;
level.drop_painting(&stored.entity);
}
} else {
stored.entity.move_entity(level, motion);
}
level.entities.finish_tick(stored);
}
}
}
// ---- Extension ----------------------------------------------
/// The base an extension with this facing belongs to.
fn base(level: &mut Level, [x, y, z]: [i32; 3]) -> Option<[i32; 3]> {
let facing = level.metadata(x, y, z) & 7;
(facing <= 5).then(|| step([x, y, z], OPPOSITE[usize::from(facing)], 1))
}
/// `BlockPistonExtension.onBlockRemoval`: an extended base without its head breaks.
pub fn head_removed(level: &mut Level, x: i32, y: i32, z: i32) {
if let Some([bx, by, bz]) = base(level, [x, y, z]) {
let base = level.block(bx, by, bz);
if is_piston(base) && level.metadata(bx, by, bz) & 8 != 0 {
break_block(level, base, bx, by, bz);
}
}
}
/// `BlockPistonExtension.onNeighborBlockChange`: passes the change on to its base.
pub fn head_neighbor_changed(level: &mut Level, x: i32, y: i32, z: i32) {
if let Some([bx, by, bz]) = base(level, [x, y, z]) {
let base = level.block(bx, by, bz);
if is_piston(base) {
update(level, base, bx, by, bz);
} else {
level.set_block_notify(x, y, z, Block::AIR);
}
}
}
impl Level {
/// The moving piston's carried block, for drops (`BlockPistonMoving.dropBlockAsItemWithChance`).
pub(crate) fn moving_block(&self, x: i32, y: i32, z: i32) -> Option<(Block, u8)> {
match self.tile_entity(x, y, z)? {
&TileEntity::Piston {
block, metadata, ..
} => Some((Block(block as u8), metadata as u8)),
_ => None,
}
}
}