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

use std::collections::VecDeque;

use common::world::light::LightKind;
use common::world::shape::BlockView;
use common::world::{Block, Material};

use crate::level::Level;
use crate::populate::trees::Kind;

pub fn random_tick(level: &mut Level, block: Block, x: i32, y: i32, z: i32) {
    match block {
        Block::FIRE => super::fire::tick(level, x, y, z),
        Block::LIT_REDSTONE_ORE => {
            level.set_block_notify(x, y, z, Block::REDSTONE_ORE);
        }
        Block::GRASS => {
            let light = level.daylight(x, y + 1, z);
            if light < 4 && level.block(x, y + 1, z).light_opacity() > 2 {
                if level.rand.next_int(4) == 0 {
                    level.set_block_notify(x, y, z, Block::DIRT);
                }
            } else if light >= 9 {
                let (x, y, z) = (
                    x + level.rand.next_int(3) - 1,
                    y + level.rand.next_int(5) - 3,
                    z + level.rand.next_int(3) - 1,
                );
                if level.block(x, y, z) == Block::DIRT
                    && level.daylight(x, y + 1, z) >= 4
                    && level.block(x, y + 1, z).light_opacity() <= 2
                {
                    level.set_block_notify(x, y, z, Block::GRASS);
                }
            }
        }
        Block::WHEAT => {
            super::plants::check_stay(level, block, x, y, z);
            if level.block(x, y, z) != block || level.daylight(x, y + 1, z) < 9 {
                return;
            }
            let meta = level.metadata(x, y, z);
            if meta < 7
                && level
                    .rand
                    .next_int((100.0 / crop_rate(level, x, y, z)) as i32)
                    == 0
            {
                level.set_metadata_notify(x, y, z, meta + 1);
            }
        }
        Block::FARMLAND if level.rand.next_int(5) == 0 => {
            let wet = (x - 4..=x + 4).any(|x| {
                (y..=y + 1)
                    .any(|y| (z - 4..=z + 4).any(|z| level.material(x, y, z) == Material::Water))
            }) || level.rained_on(x, y + 1, z);
            let meta = level.metadata(x, y, z);
            if wet {
                level.set_metadata_notify(x, y, z, 7);
            } else if meta > 0 {
                level.set_metadata_notify(x, y, z, meta - 1);
            } else if level.block(x, y + 1, z) != Block::WHEAT {
                level.set_block_notify(x, y, z, Block::DIRT);
            }
        }
        Block::LEAVES => decay(level, x, y, z),
        Block::SAPLING => {
            super::plants::check_stay(level, block, x, y, z);
            if level.block(x, y, z) == block
                && level.daylight(x, y + 1, z) >= 9
                && level.rand.next_int(30) == 0
            {
                let meta = level.metadata(x, y, z);
                if meta & 8 == 0 {
                    level.set_metadata_notify(x, y, z, meta | 8);
                } else {
                    grow_tree(level, x, y, z);
                }
            }
        }
        Block::SUGAR_CANE | Block::CACTUS => {
            if y == 127 || !level.is_air(x, y + 1, z) {
                return;
            }
            let mut height = 1;
            while level.block(x, y - height, z) == block {
                height += 1;
            }
            if height >= 3 {
                return;
            }
            let meta = level.metadata(x, y, z);
            if meta == 15 {
                level.set_block_notify(x, y + 1, z, block);
                level.set_metadata_notify(x, y, z, 0);
            } else {
                level.set_metadata_notify(x, y, z, meta + 1);
            }
        }
        Block::DANDELION | Block::ROSE | Block::TALL_GRASS | Block::DEAD_BUSH => {
            super::plants::check_stay(level, block, x, y, z)
        }
        Block::BROWN_MUSHROOM | Block::RED_MUSHROOM => spread_mushroom(level, block, x, y, z),
        Block::LAVA => ignite_from_lava(level, x, y, z),
        Block::ICE if level.saved_light(LightKind::Block, x, y, z) > 11 - block.light_opacity() => {
            level.set_block_notify(x, y, z, Block::WATER);
        }
        Block::SNOW_LAYER | Block::SNOW if level.saved_light(LightKind::Block, x, y, z) > 11 => {
            super::break_block(level, block, x, y, z)
        }
        Block::TORCH if level.metadata(x, y, z) == 0 => {
            super::placement::orient_torch(level, block, x, y, z)
        }
        Block::LOCKED_CHEST => {
            level.set_block_notify(x, y, z, Block::AIR);
        }
        Block::FLOWING_WATER
        | Block::FLOWING_LAVA
        | Block::REDSTONE_TORCH
        | Block::UNLIT_REDSTONE_TORCH
        | Block::BUTTON
        | Block::STONE_PRESSURE_PLATE
        | Block::WOODEN_PRESSURE_PLATE
        | Block::DETECTOR_RAIL => super::update_tick(level, block, x, y, z),
        _ => {}
    }
}

/// `BlockMushroom.updateTick`: rarely spreads to a dark spot nearby.
fn spread_mushroom(level: &mut Level, block: Block, x: i32, y: i32, z: i32) {
    let random = &mut level.rand;
    if random.next_int(100) != 0 {
        return;
    }
    let tx = x + random.next_int(3) - 1;
    let ty = y + random.next_int(2) - random.next_int(2);
    let tz = z + random.next_int(3) - 1;
    if level.is_air(tx, ty, tz) && super::can_stay(level, block, tx, ty, tz) {
        // The original also draws an unused second offset
        level.rand.next_int(3);
        level.rand.next_int(3);
        if level.is_air(tx, ty, tz) && super::can_stay(level, block, tx, ty, tz) {
            level.set_block_notify(tx, ty, tz, block);
        }
    }
}

/// `BlockStationary.updateTick` for lava: may set fire to air above it next to flammable blocks.
fn ignite_from_lava(level: &mut Level, mut x: i32, mut y: i32, mut z: i32) {
    for _ in 0..level.rand.next_int(3) {
        x += level.rand.next_int(3) - 1;
        y += 1;
        z += level.rand.next_int(3) - 1;
        let block = level.block(x, y, z);
        if block.is_air() {
            let flammable = common::interaction::OFFSETS
                .iter()
                .any(|[dx, dy, dz]| level.material(x + dx, y + dy, z + dz).can_burn());
            if flammable {
                level.set_block_notify(x, y, z, Block::FIRE);
                return;
            }
        } else if block.material().is_solid() {
            return;
        }
    }
}

fn crop_rate(level: &Level, x: i32, y: i32, z: i32) -> f32 {
    let mut rate = 1.0;
    for dx in -1..=1 {
        for dz in -1..=1 {
            if BlockView::block(level, x + dx, y - 1, z + dz) == Block::FARMLAND {
                let wet = BlockView::metadata(level, x + dx, y - 1, z + dz) > 0;
                let soil = if wet { 3.0 } else { 1.0 };
                rate += if dx == 0 && dz == 0 { soil } else { soil / 4.0 };
            }
        }
    }
    let crop = |dx, dz| BlockView::block(level, x + dx, y, z + dz) == Block::WHEAT;
    if [-1, 1]
        .into_iter()
        .any(|dx| [-1, 1].into_iter().any(|dz| crop(dx, dz)))
        || (crop(-1, 0) || crop(1, 0)) && (crop(0, -1) || crop(0, 1))
    {
        rate /= 2.0;
    }
    rate
}

/// `BlockLog.onBlockRemoval` (`only_new: true`) and `BlockLeaves.onBlockRemoval`: flags
/// nearby leaves to check for decay.
pub fn mark_leaves(level: &mut Level, x: i32, y: i32, z: i32, radius: i32, only_new: bool) {
    if !level.chunks_exist_around(x, y, z, radius + 1) {
        return;
    }
    for dx in -radius..=radius {
        for dy in -radius..=radius {
            for dz in -radius..=radius {
                let (lx, ly, lz) = (x + dx, y + dy, z + dz);
                if level.block(lx, ly, lz) == Block::LEAVES {
                    let metadata = level.metadata(lx, ly, lz);
                    if !only_new || metadata & 8 == 0 {
                        level.set_metadata(lx, ly, lz, metadata | 8);
                    }
                }
            }
        }
    }
}

fn decay(level: &mut Level, x: i32, y: i32, z: i32) {
    let meta = level.metadata(x, y, z);
    if meta & 8 == 0 || !level.chunks_exist_around(x, y, z, 5) {
        return;
    }
    // Search outwards from this leaf instead of repeatedly scanning a 32³ array
    let mut seen = [false; 9 * 9 * 9];
    let mut pending = VecDeque::with_capacity(129);
    pending.push_back(([0_i32; 3], 0));
    seen[4 * 81 + 4 * 9 + 4] = true;
    while let Some(([dx, dy, dz], distance)) = pending.pop_front() {
        for [ox, oy, oz] in common::interaction::OFFSETS {
            let (nx, ny, nz) = (dx + ox, dy + oy, dz + oz);
            let block = BlockView::block(level, x + nx, y + ny, z + nz);
            if block == Block::LOG {
                level.set_metadata(x, y, z, meta & !8);
                return;
            }
            if block != Block::LEAVES || distance >= 3 {
                continue;
            }
            let index = ((nx + 4) * 81 + (ny + 4) * 9 + nz + 4) as usize;
            if !seen[index] {
                seen[index] = true;
                pending.push_back(([nx, ny, nz], distance + 1));
            }
        }
    }
    super::break_block(level, Block::LEAVES, x, y, z);
}

pub fn grow_tree(level: &mut Level, x: i32, y: i32, z: i32) {
    if !level.chunks_exist_around(x, y, z, 8) {
        return;
    }
    let meta = level.metadata(x, y, z) & 3;
    let kind = match meta {
        1 => Kind::Spruce,
        2 => Kind::Birch,
        _ if level.rand.next_int(10) == 0 => Kind::SaplingBigOak,
        _ => Kind::Oak,
    };
    level.set_block(x, y, z, Block::AIR);
    let mut random = level.rand.clone();
    let grown = kind.generate(level, [x, y, z], &mut random);
    level.rand = random;
    if !grown {
        level.set_block_with_metadata(x, y, z, Block::SAPLING, meta);
    }
}

/// Bone meal on grass (`ItemDye.onItemUse`): tall grass and flowers on the grass around it.
pub fn bone_meal_grass(level: &mut Level, x: i32, y: i32, z: i32) {
    'attempt: for attempt in 0..128 {
        let [mut px, mut py, mut pz] = [x, y + 1, z];
        for _ in 0..attempt / 16 {
            let random = &mut level.block_rand;
            px += random.next_int(3) - 1;
            py += (random.next_int(3) - 1) * random.next_int(3) / 2;
            pz += random.next_int(3) - 1;
            if level.block(px, py - 1, pz) != Block::GRASS || level.is_normal_cube(px, py, pz) {
                continue 'attempt;
            }
        }
        if level.is_air(px, py, pz) {
            let random = &mut level.block_rand;
            if random.next_int(10) != 0 {
                level.set_block_with_metadata_notify(px, py, pz, Block::TALL_GRASS, 1);
            } else if random.next_int(3) != 0 {
                level.set_block_notify(px, py, pz, Block::DANDELION);
            } else {
                level.set_block_notify(px, py, pz, Block::ROSE);
            }
        }
    }
}