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 fluid.rs
//! Port of `BlockFluid`, `BlockFlowing` and `BlockStationary`.
use common::protocol::Packet;
use common::world::{Block, Material};
use super::{SIDES, drop_as_item, tick_rate};
use crate::level::Level;
/// `BlockFlowing.isOptimalFlowDirection` of the water and lava instances. A flowing update
/// reads it after every spread, so nested immediate ticks can change where it spreads next.
#[derive(Default)]
pub struct FlowState {
optimal: [[bool; 4]; 2],
}
/// `BlockFluid.checkForHarden`: lava touching water turns into obsidian or cobblestone.
pub fn check_for_harden(level: &mut Level, block: Block, x: i32, y: i32, z: i32) {
if level.block(x, y, z) != block || block.material() != Material::Lava {
return;
}
let touches_water = [(0, 0, -1), (0, 0, 1), (-1, 0, 0), (1, 0, 0), (0, 1, 0)]
.into_iter()
.any(|(dx, dy, dz)| level.material(x + dx, y + dy, z + dz) == Material::Water);
if touches_water {
let metadata = level.metadata(x, y, z);
if metadata <= 4 {
let hardened = if metadata == 0 {
Block::OBSIDIAN
} else {
Block::COBBLESTONE
};
level.set_block_notify(x, y, z, hardened);
}
trigger_lava_mix_effects(level, [x, y, z]);
}
}
/// The fizz of lava hardening and redstone torches burning out.
pub(super) fn fizz(level: &mut Level, position: [i32; 3]) {
let pitch = 2.6 + (level.rand.next_float() - level.rand.next_float()) * 0.8;
level.events.push(Packet::Sound {
name: "random.fizz".into(),
pos: position.map(|v| f64::from(v as f32 + 0.5)),
volume: 0.5,
pitch,
});
}
/// `BlockFluid.triggerLavaMixEffects`. Only the sound pitch draws from the world random.
fn trigger_lava_mix_effects(level: &mut Level, [x, y, z]: [i32; 3]) {
fizz(level, [x, y, z]);
for _ in 0..8 {
let pos = [
f64::from(x) + level.effect_rand.next_double(),
f64::from(y) + 1.2,
f64::from(z) + level.effect_rand.next_double(),
];
level.events.push(Packet::Particle {
name: "largesmoke".into(),
pos,
velocity: [0.0; 3],
});
}
}
/// `BlockFlowing.onBlockAdded`.
pub fn on_flowing_added(level: &mut Level, block: Block, x: i32, y: i32, z: i32) {
check_for_harden(level, block, x, y, z);
if level.block(x, y, z) == block {
level.schedule_tick(x, y, z, block, tick_rate(block));
}
}
/// `BlockStationary.onNeighborBlockChange`: wakes still fluid up as flowing fluid.
pub fn on_still_neighbor_changed(level: &mut Level, block: Block, x: i32, y: i32, z: i32) {
check_for_harden(level, block, x, y, z);
if level.block(x, y, z) == block {
let flowing = Block(block.0 - 1);
let metadata = level.metadata(x, y, z);
level.editing_blocks = true;
level.set_block_with_metadata(x, y, z, flowing, metadata);
level.schedule_tick(x, y, z, flowing, tick_rate(flowing));
level.editing_blocks = false;
}
}
/// `BlockFlowing.updateTick`.
pub fn update_flowing(level: &mut Level, block: Block, x: i32, y: i32, z: i32) {
let fluid = Flowing(block);
let lava = block.material() == Material::Lava;
let step = if lava && level.has_sky() { 2 } else { 1 };
let mut decay = fluid.flow_decay(level, x, y, z);
let mut settles = true;
if decay > 0 {
// `getSmallestFlowDecay` of the sides, counting the sources among them
let mut sources = 0;
let mut smallest = -100;
for [dx, dz] in SIDES {
let decay = fluid.flow_decay(level, x + dx, y, z + dz);
if decay < 0 {
continue;
}
sources += u32::from(decay == 0);
let decay = if decay >= 8 { 0 } else { decay };
if smallest < 0 || decay < smallest {
smallest = decay;
}
}
let mut target = smallest + step;
if target >= 8 || smallest < 0 {
target = -1;
}
let above = fluid.flow_decay(level, x, y + 1, z);
if above >= 0 {
target = if above >= 8 { above } else { above + 8 };
}
if sources >= 2 && !lava {
let below = level.material(x, y - 1, z);
if below.is_solid() || (below == block.material() && level.metadata(x, y, z) == 0) {
target = 0;
}
}
if lava && decay < 8 && target < 8 && target > decay && level.rand.next_int(4) != 0 {
target = decay;
settles = false;
}
if target != decay {
decay = target;
if target < 0 {
level.set_block_notify(x, y, z, Block::AIR);
} else {
level.set_metadata_notify(x, y, z, target as u8);
level.schedule_tick(x, y, z, block, tick_rate(block));
level.notify_neighbors(x, y, z, block);
}
} else if settles {
fluid.settle(level, x, y, z);
}
} else {
fluid.settle(level, x, y, z);
}
if fluid.can_displace(level, x, y - 1, z) {
let below = if decay >= 8 { decay } else { decay + 8 };
level.set_block_with_metadata_notify(x, y - 1, z, block, below as u8);
} else if decay >= 0 && (decay == 0 || fluid.blocks_flow(level, x, y - 1, z)) {
fluid.update_optimal_directions(level, x, y, z);
let spread = if decay >= 8 { 1 } else { decay + step };
if spread >= 8 {
return;
}
for (side, [dx, dz]) in SIDES.into_iter().enumerate() {
if level.flow.optimal[usize::from(lava)][side] {
fluid.flow_into(level, x + dx, y, z + dz, spread);
}
}
}
}
struct Flowing(Block);
impl Flowing {
fn material(&self) -> Material {
self.0.material()
}
/// `getFlowDecay`: the fluid level, or -1 for other materials.
fn flow_decay(&self, level: &mut Level, x: i32, y: i32, z: i32) -> i32 {
if level.material(x, y, z) == self.material() {
i32::from(level.metadata(x, y, z))
} else {
-1
}
}
/// `updateFlow`: turns the block into still fluid.
fn settle(&self, level: &mut Level, x: i32, y: i32, z: i32) {
let metadata = level.metadata(x, y, z);
level.set_block_with_metadata(x, y, z, Block(self.0.0 + 1), metadata);
}
/// `blockBlocksFlow`.
fn blocks_flow(&self, level: &mut Level, x: i32, y: i32, z: i32) -> bool {
match level.block(x, y, z) {
Block::WOODEN_DOOR
| Block::IRON_DOOR
| Block::SIGN
| Block::LADDER
| Block::SUGAR_CANE => true,
Block::AIR => false,
block => block.material().is_solid(),
}
}
/// `liquidCanDisplaceBlock`.
fn can_displace(&self, level: &mut Level, x: i32, y: i32, z: i32) -> bool {
let material = level.material(x, y, z);
material != self.material()
&& material != Material::Lava
&& !self.blocks_flow(level, x, y, z)
}
/// Whether flow may enter a side: open, and not already a source of this fluid.
fn is_open(&self, level: &mut Level, x: i32, y: i32, z: i32) -> bool {
!self.blocks_flow(level, x, y, z)
&& (level.material(x, y, z) != self.material() || level.metadata(x, y, z) != 0)
}
/// `flowIntoBlock`.
fn flow_into(&self, level: &mut Level, x: i32, y: i32, z: i32, decay: i32) {
if !self.can_displace(level, x, y, z) {
return;
}
let existing = level.block(x, y, z);
if !existing.is_air() {
if self.material() == Material::Lava {
trigger_lava_mix_effects(level, [x, y, z]);
} else {
let metadata = level.metadata(x, y, z);
drop_as_item(level, existing, x, y, z, metadata);
}
}
level.set_block_with_metadata_notify(x, y, z, self.0, decay as u8);
}
/// `calculateFlowCost`: distance to the nearest drop, searching up to four blocks.
fn flow_cost(&self, level: &mut Level, [x, y, z]: [i32; 3], distance: i32, from: usize) -> i32 {
let mut cost = 1000;
for (side, [dx, dz]) in SIDES.into_iter().enumerate() {
if side == from ^ 1 {
continue;
}
let (nx, nz) = (x + dx, z + dz);
if !self.is_open(level, nx, y, nz) {
continue;
}
if !self.blocks_flow(level, nx, y - 1, nz) {
return distance;
}
if distance < 4 {
cost = cost.min(self.flow_cost(level, [nx, y, nz], distance + 1, side));
}
}
cost
}
/// `getOptimalFlowDirections`.
fn update_optimal_directions(&self, level: &mut Level, x: i32, y: i32, z: i32) {
let mut costs = [1000; 4];
for (side, [dx, dz]) in SIDES.into_iter().enumerate() {
let (nx, nz) = (x + dx, z + dz);
if self.is_open(level, nx, y, nz) {
costs[side] = if self.blocks_flow(level, nx, y - 1, nz) {
self.flow_cost(level, [nx, y, nz], 1, side)
} else {
0
};
}
}
let min = costs.into_iter().min().unwrap_or(1000);
level.flow.optimal[usize::from(self.material() == Material::Lava)] =
costs.map(|cost| cost == min);
}
}