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 rail.rs
//! Rails: `BlockRail`, its `RailLogic` connections, and powered and detector rails.
use common::world::Block;
use super::{break_block, redstone};
use crate::level::Level;
pub fn is_rail(block: Block) -> bool {
block == Block::RAIL || is_straight_only(block)
}
/// Powered and detector rails cannot curve and keep their state in bit 8.
fn is_straight_only(block: Block) -> bool {
matches!(block, Block::POWERED_RAIL | Block::DETECTOR_RAIL)
}
fn rail_at(level: &mut Level, x: i32, y: i32, z: i32) -> bool {
is_rail(level.block(x, y, z))
}
/// `RailLogic`: a rail and the positions its shape connects to.
struct Rail {
position: [i32; 3],
straight_only: bool,
connections: Vec<[i32; 3]>,
}
/// `setConnections`: the neighbors a rail shape leads to.
fn connections([x, y, z]: [i32; 3], shape: u8) -> Vec<[i32; 3]> {
let pair = |a: [i32; 3], b: [i32; 3]| vec![a, b];
match shape {
0 => pair([x, y, z - 1], [x, y, z + 1]),
1 => pair([x - 1, y, z], [x + 1, y, z]),
2 => pair([x - 1, y, z], [x + 1, y + 1, z]),
3 => pair([x - 1, y + 1, z], [x + 1, y, z]),
4 => pair([x, y + 1, z - 1], [x, y, z + 1]),
5 => pair([x, y, z - 1], [x, y + 1, z + 1]),
6 => pair([x + 1, y, z], [x, y, z + 1]),
7 => pair([x - 1, y, z], [x, y, z + 1]),
8 => pair([x - 1, y, z], [x, y, z - 1]),
9 => pair([x + 1, y, z], [x, y, z - 1]),
_ => Vec::new(),
}
}
/// The straight shape along the connected axis, x winning over z; -1 for none.
fn straight(along_z: bool, along_x: bool) -> i32 {
if along_x {
1
} else if along_z {
0
} else {
-1
}
}
impl Rail {
fn new(level: &mut Level, position @ [x, y, z]: [i32; 3]) -> Self {
let straight_only = is_straight_only(level.block(x, y, z));
let mut shape = level.metadata(x, y, z);
if straight_only {
shape &= 7;
}
Self {
position,
straight_only,
connections: connections(position, shape),
}
}
/// `getMinecartTrackLogic`: the rail at, above or below `position`.
fn at(level: &mut Level, [x, y, z]: [i32; 3]) -> Option<Self> {
[y, y + 1, y - 1]
.into_iter()
.find(|&y| rail_at(level, x, y, z))
.map(|y| Self::new(level, [x, y, z]))
}
/// `refreshConnectedTracks`: keeps only the connections to rails connecting back.
fn refresh(&mut self, level: &mut Level) {
let [x, _, z] = self.position;
self.connections.retain_mut(|connection| {
let other = Self::at(level, *connection).filter(|other| other.links(x, z));
other
.inspect(|other| *connection = other.position)
.is_some()
});
}
fn connected_to(&self, other: &Rail) -> bool {
self.links(other.position[0], other.position[2])
}
/// `isInTrack`: whether a connection leads to the column.
fn links(&self, x: i32, z: i32) -> bool {
self.connections
.iter()
.any(|&[cx, _, cz]| cx == x && cz == z)
}
/// `getAdjacentTracks`.
fn adjacent(&self, level: &mut Level) -> usize {
let [x, y, z] = self.position;
[(0, -1), (0, 1), (-1, 0), (1, 0)]
.into_iter()
.filter(|&(dx, dz)| (y - 1..=y + 1).any(|y| rail_at(level, x + dx, y, z + dz)))
.count()
}
/// `handleKeyPress`: whether the rail can take one more connection to `other`.
fn accepts(&self, other: &Rail) -> bool {
self.connected_to(other) || self.connections.len() != 2
}
/// Slopes a straight shape up towards a rail one block higher; no shape becomes flat.
fn slope(&self, level: &mut Level, shape: i32) -> u8 {
let [x, y, z] = self.position;
let (rises, [first, second]) = match shape {
0 => ([[0, -1], [0, 1]], [4, 5]),
1 => ([[1, 0], [-1, 0]], [2, 3]),
_ => return shape.max(0) as u8,
};
let [a, b] = rises.map(|[dx, dz]| rail_at(level, x + dx, y + 1, z + dz));
if b {
second
} else if a {
first
} else {
shape as u8
}
}
fn store(&self, level: &mut Level, shape: u8) -> u8 {
let [x, y, z] = self.position;
if self.straight_only {
level.metadata(x, y, z) & 8 | shape
} else {
shape
}
}
/// The curve joining exactly two neighbors at a right angle, unless the rail cannot curve.
fn curve(&self, [north, south, west, east]: [bool; 4]) -> Option<i32> {
match [north, south, west, east] {
_ if self.straight_only => None,
[false, true, false, true] => Some(6),
[false, true, true, false] => Some(7),
[true, false, true, false] => Some(8),
[true, false, false, true] => Some(9),
_ => None,
}
}
/// `connectToNeighbor`.
fn connect(&mut self, level: &mut Level, other: &Rail) {
self.connections.push(other.position);
let [x, _, z] = self.position;
let sides @ [north, south, west, east] =
[[0, -1], [0, 1], [-1, 0], [1, 0]].map(|[dx, dz]| self.links(x + dx, z + dz));
let straight = straight(north || south, west || east);
let shape = self.slope(level, self.curve(sides).unwrap_or(straight));
let metadata = self.store(level, shape);
let [x, y, z] = self.position;
level.set_metadata_notify(x, y, z, metadata);
}
/// `canConnectFrom`.
fn can_connect_from(&self, level: &mut Level, position: [i32; 3]) -> bool {
Self::at(level, position).is_some_and(|mut other| {
other.refresh(level);
other.accepts(self)
})
}
/// `refreshTrackShape`: picks the shape from the neighbors that can connect, preferring the
/// curves a powered signal selects.
fn refresh_shape(&mut self, level: &mut Level, powered: bool, force: bool) {
let [x, y, z] = self.position;
let sides @ [north, south, west, east] = [[0, -1], [0, 1], [-1, 0], [1, 0]]
.map(|[dx, dz]| self.can_connect_from(level, [x + dx, y, z + dz]));
let (along_z, along_x) = (north || south, west || east);
let mut shape = self.curve(sides).unwrap_or(if along_z != along_x {
straight(along_z, along_x)
} else {
-1
});
if shape == -1 {
shape = straight(along_z, along_x);
if !self.straight_only {
let curves: [(bool, i32); 4] = if powered {
[
(south && east, 6),
(west && south, 7),
(east && north, 9),
(north && west, 8),
]
} else {
[
(north && west, 8),
(east && north, 9),
(west && south, 7),
(south && east, 6),
]
};
for (applies, curve) in curves {
if applies {
shape = curve;
}
}
}
}
let shape = self.slope(level, shape);
self.connections = connections(self.position, shape);
let metadata = self.store(level, shape);
if force || level.metadata(x, y, z) != metadata {
level.set_metadata_notify(x, y, z, metadata);
for index in 0..self.connections.len() {
let Some(position) = self.connections.get(index).copied() else {
break;
};
if let Some(mut other) = Self::at(level, position) {
other.refresh(level);
if other.accepts(self) {
other.connect(level, self);
}
}
}
}
}
}
pub fn added(level: &mut Level, x: i32, y: i32, z: i32) {
let powered = level.powered(x, y, z);
Rail::new(level, [x, y, z]).refresh_shape(level, powered, true);
}
/// `BlockRail.onNeighborBlockChange`: drops without support, switches powered rails and
/// re-shapes plain rails at junctions when powered.
pub fn neighbor_changed(level: &mut Level, block: Block, x: i32, y: i32, z: i32, neighbor: Block) {
let meta = level.metadata(x, y, z);
let shape = if is_straight_only(block) {
meta & 7
} else {
meta
};
let unsupported = !level.is_normal_cube(x, y - 1, z)
|| shape == 2 && !level.is_normal_cube(x + 1, y, z)
|| shape == 3 && !level.is_normal_cube(x - 1, y, z)
|| shape == 4 && !level.is_normal_cube(x, y, z - 1)
|| shape == 5 && !level.is_normal_cube(x, y, z + 1);
if unsupported {
break_block(level, block, x, y, z);
} else if block == Block::POWERED_RAIL {
let powered = level.powered_or_above(x, y, z)
|| neighbor_powered(level, [x, y, z], meta, true, 0)
|| neighbor_powered(level, [x, y, z], meta, false, 0);
if powered != (meta & 8 != 0) {
level.set_metadata_notify(x, y, z, shape | if powered { 8 } else { 0 });
level.notify_neighbors(x, y - 1, z, block);
if (2..=5).contains(&shape) {
level.notify_neighbors(x, y + 1, z, block);
}
}
} else if !neighbor.is_air()
&& redstone::can_provide_power(level, neighbor)
&& block == Block::RAIL
&& Rail::new(level, [x, y, z]).adjacent(level) == 3
{
let powered = level.powered(x, y, z);
Rail::new(level, [x, y, z]).refresh_shape(level, powered, false);
}
}
/// `isNeighborRailPowered`: follows the powered rail line up to eight rails for a power source.
fn neighbor_powered(
level: &mut Level,
[x, y, z]: [i32; 3],
meta: u8,
towards_negative: bool,
depth: u32,
) -> bool {
if depth >= 8 {
return false;
}
// Up or down the line, rising on slopes that go up that way
let sign = if towards_negative { -1 } else { 1 };
let rises = matches!((meta & 7, towards_negative), (2 | 4, false) | (3 | 5, true));
let (dx, dz, shape) = match meta & 7 {
0 | 4 | 5 => (0, -sign, 0),
1..=3 => (sign, 0, 1),
shape => (0, 0, shape),
};
let [x, y, z] = [x + dx, y + i32::from(rises), z + dz];
passes_power(level, [x, y, z], towards_negative, depth, shape)
|| !rises && passes_power(level, [x, y - 1, z], towards_negative, depth, shape)
}
/// `isRailPassingPower`.
fn passes_power(
level: &mut Level,
[x, y, z]: [i32; 3],
towards_negative: bool,
depth: u32,
shape: u8,
) -> bool {
if level.block(x, y, z) != Block::POWERED_RAIL {
return false;
}
let meta = level.metadata(x, y, z);
let other = meta & 7;
if shape == 1 && matches!(other, 0 | 4 | 5)
|| shape == 0 && matches!(other, 1..=3)
|| meta & 8 == 0
{
return false;
}
level.powered_or_above(x, y, z)
|| neighbor_powered(level, [x, y, z], meta, towards_negative, depth + 1)
}
/// `BlockDetectorRail.setStateIfMinecartInteractsWithRail`.
pub fn update_detector(level: &mut Level, x: i32, y: i32, z: i32, extra: bool) {
let meta = level.metadata(x, y, z);
let pressed = meta & 8 != 0;
let area = redstone::plate_area(x, y, z);
let occupied = extra
|| level
.entities
.iter()
.any(|e| !e.entity.dead && e.is_minecart() && e.entity.bb.intersects(&area));
if occupied != pressed {
level.set_metadata_notify(x, y, z, if occupied { meta | 8 } else { meta & 7 });
level.notify_neighbors(x, y, z, Block::DETECTOR_RAIL);
level.notify_neighbors(x, y - 1, z, Block::DETECTOR_RAIL);
}
if occupied {
level.schedule_tick(x, y, z, Block::DETECTOR_RAIL, 20);
}
}