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