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