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 / portal.rs

//! `BlockPortal` frames and the `Teleporter` that finds or builds the portal on the other side.

use common::entity::Entity;
use common::math::floor_f64;
use common::world::Block;
use glam::DVec3;

use crate::level::Level;

/// `BlockPortal.tryToCreatePortal`: fills an empty obsidian frame around fire with portal.
pub fn try_create(level: &mut Level, mut x: i32, y: i32, mut z: i32) -> bool {
    let dx = i32::from(
        level.block(x - 1, y, z) == Block::OBSIDIAN || level.block(x + 1, y, z) == Block::OBSIDIAN,
    );
    let dz = i32::from(
        level.block(x, y, z - 1) == Block::OBSIDIAN || level.block(x, y, z + 1) == Block::OBSIDIAN,
    );
    if dx == dz {
        return false;
    }
    if level.is_air(x - dx, y, z - dz) {
        x -= dx;
        z -= dz;
    }
    for i in -1..=2 {
        for j in -1..=3 {
            let (side, end) = (i == -1 || i == 2, j == -1 || j == 3);
            if side && end {
                continue;
            }
            let block = level.block(x + dx * i, y + j, z + dz * i);
            let frame = side || end;
            if frame && block != Block::OBSIDIAN
                || !frame && !block.is_air() && block != Block::FIRE
            {
                return false;
            }
        }
    }
    level.editing_blocks = true;
    for i in 0..2 {
        for j in 0..3 {
            level.set_block_notify(x + dx * i, y + j, z + dz * i, Block::PORTAL);
        }
    }
    level.editing_blocks = false;
    true
}

/// `BlockPortal.onNeighborBlockChange`: breaks the portal once its frame is incomplete.
pub fn neighbor_changed(level: &mut Level, x: i32, y: i32, z: i32) {
    let along_x =
        level.block(x - 1, y, z) == Block::PORTAL || level.block(x + 1, y, z) == Block::PORTAL;
    let (dx, dz) = if along_x { (1, 0) } else { (0, 1) };
    let mut bottom = y;
    while level.block(x, bottom - 1, z) == Block::PORTAL {
        bottom -= 1;
    }
    if level.block(x, bottom - 1, z) != Block::OBSIDIAN {
        level.set_block_notify(x, y, z, Block::AIR);
        return;
    }
    let mut height = 1;
    while height < 4 && level.block(x, bottom + height, z) == Block::PORTAL {
        height += 1;
    }
    let intact = height == 3 && level.block(x, bottom + height, z) == Block::OBSIDIAN && {
        let along_z =
            level.block(x, y, z - 1) == Block::PORTAL || level.block(x, y, z + 1) == Block::PORTAL;
        let (ahead, behind) = (
            level.block(x + dx, y, z + dz),
            level.block(x - dx, y, z - dz),
        );
        !(along_x && along_z)
            && ((ahead == Block::OBSIDIAN && behind == Block::PORTAL)
                || (behind == Block::OBSIDIAN && ahead == Block::PORTAL))
    };
    if !intact {
        level.set_block_notify(x, y, z, Block::AIR);
    }
}

// ---- Teleporter -------------------------------------------------

/// `Teleporter.setExitLocation`: moves the entity into the nearest portal, building one first
/// if there is none.
pub fn place_in_portal(level: &mut Level, entity: &mut Entity) {
    if !find_exit(level, entity) {
        create_exit(level, entity);
        find_exit(level, entity);
    }
}

/// `Teleporter.findExitLocation`: the closest portal within 128 blocks.
fn find_exit(level: &mut Level, entity: &mut Entity) -> bool {
    const RANGE: i32 = 128;
    let (px, pz) = (floor_f64(entity.pos.x), floor_f64(entity.pos.z));
    let mut best: Option<(f64, [i32; 3])> = None;
    for x in px - RANGE..=px + RANGE {
        let dx = f64::from(x) + 0.5 - entity.pos.x;
        for z in pz - RANGE..=pz + RANGE {
            let dz = f64::from(z) + 0.5 - entity.pos.z;
            let mut y = 127;
            while y >= 0 {
                if level.block(x, y, z) == Block::PORTAL {
                    while level.block(x, y - 1, z) == Block::PORTAL {
                        y -= 1;
                    }
                    let dy = f64::from(y) + 0.5 - entity.pos.y;
                    let distance = dx * dx + dy * dy + dz * dz;
                    if best.is_none_or(|(closest, _)| distance < closest) {
                        best = Some((distance, [x, y, z]));
                    }
                }
                y -= 1;
            }
        }
    }
    let Some((_, [x, y, z])) = best else {
        return false;
    };
    let mut target = DVec3::new(f64::from(x), f64::from(y), f64::from(z)) + 0.5;
    for [dx, dz] in super::SIDES {
        if level.block(x + dx, y, z + dz) == Block::PORTAL {
            target += DVec3::new(f64::from(dx), 0.0, f64::from(dz)) * 0.5;
        }
    }
    entity.set_location_and_angles(target, entity.yaw, 0.0);
    entity.motion = DVec3::ZERO;
    true
}

/// Whether a portal of `depth` blocks across `width` fits at a floor `y`, oriented by `(dx, dz)`
/// and offset sideways by `(sx, sz)`: solid below, air in the frame's space.
fn fits(
    level: &mut Level,
    [x, y, z]: [i32; 3],
    (dx, dz): (i32, i32),
    (sx, sz): (i32, i32),
    depth: i32,
) -> bool {
    for i in 0..depth {
        for j in 0..4 {
            for k in -1..4 {
                let (bx, by, bz) = (x + (j - 1) * dx + i * sx, y + k, z + (j - 1) * dz + i * sz);
                let ok = if k < 0 {
                    level.material(bx, by, bz).is_solid()
                } else {
                    level.is_air(bx, by, bz)
                };
                if !ok {
                    return false;
                }
            }
        }
    }
    true
}

/// The axis of an orientation index of `createExitLocation`.
fn axis(orientation: i32) -> (i32, i32) {
    let (dx, dz) = (orientation % 2, 1 - orientation % 2);
    if orientation % 4 >= 2 {
        (-dx, -dz)
    } else {
        (dx, dz)
    }
}

/// `Teleporter.createExitLocation`: builds a portal near the entity, on a platform if needed.
fn create_exit(level: &mut Level, entity: &Entity) {
    const RANGE: i32 = 16;
    let [px, py, pz] = entity.pos.to_array().map(floor_f64);
    let start = level.rand.next_int(4);
    let mut best: Option<(f64, [i32; 3], i32)> = None;
    for wide in [true, false] {
        if best.is_some() {
            break;
        }
        for x in px - RANGE..=px + RANGE {
            let dx = f64::from(x) + 0.5 - entity.pos.x;
            for z in pz - RANGE..=pz + RANGE {
                let dz = f64::from(z) + 0.5 - entity.pos.z;
                let mut y = 127;
                'column: while y >= 0 {
                    if level.is_air(x, y, z) {
                        while y > 0 && level.is_air(x, y - 1, z) {
                            y -= 1;
                        }
                        let count = if wide { 4 } else { 2 };
                        for orientation in start..start + count {
                            let (along, side, depth) = if wide {
                                let (dx, dz) = axis(orientation);
                                ((dx, dz), (dz, -dx), 3)
                            } else {
                                ((orientation % 2, 1 - orientation % 2), (0, 0), 1)
                            };
                            if !fits(level, [x, y, z], along, side, depth) {
                                y -= 1;
                                continue 'column;
                            }
                            let dy = f64::from(y) + 0.5 - entity.pos.y;
                            let distance = dx * dx + dy * dy + dz * dz;
                            if best.is_none_or(|(closest, ..)| distance < closest) {
                                best = Some((distance, [x, y, z], orientation % count));
                            }
                        }
                    }
                    y -= 1;
                }
            }
        }
    }
    let ([x, mut y, z], orientation) = best.map_or(([px, py, pz], 0), |(_, pos, o)| (pos, o));
    let (dx, dz) = axis(orientation);
    if best.is_none() {
        y = y.clamp(70, 118);
        for i in -1..=1 {
            for j in 1..3 {
                for k in -1..3 {
                    let block = if k < 0 { Block::OBSIDIAN } else { Block::AIR };
                    level.set_block_notify(
                        x + (j - 1) * dx + i * dz,
                        y + k,
                        z + (j - 1) * dz - i * dx,
                        block,
                    );
                }
            }
        }
    }
    for _ in 0..4 {
        level.editing_blocks = true;
        for j in 0..4 {
            for k in -1..4 {
                let frame = j == 0 || j == 3 || k == -1 || k == 3;
                let block = if frame {
                    Block::OBSIDIAN
                } else {
                    Block::PORTAL
                };
                level.set_block_notify(x + (j - 1) * dx, y + k, z + (j - 1) * dz, block);
            }
        }
        level.editing_blocks = false;
        for j in 0..4 {
            for k in -1..4 {
                let (bx, by, bz) = (x + (j - 1) * dx, y + k, z + (j - 1) * dz);
                let block = level.block(bx, by, bz);
                level.notify_neighbors(bx, by, bz, block);
            }
        }
    }
}