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