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