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 / entity / explosion.rs

use common::entity::Entity;
use common::math::floor_f64;
use common::physics::Aabb;
use common::protocol::Packet;
use common::world::shape::{self, BlockView};
use common::world::{Block, ChunkPos};
use glam::DVec3;
use rustc_hash::FxHashSet;

use crate::level::Level;

fn density(level: &Level, origin: DVec3, bounds: &Aabb) -> f32 {
    let step = DVec3::ONE / ((bounds.max - bounds.min) * 2.0 + DVec3::ONE);
    let mut clear = 0;
    let mut total = 0;
    let mut x = 0.0_f32;
    while x <= 1.0 {
        let mut y = 0.0_f32;
        while y <= 1.0 {
            let mut z = 0.0_f32;
            while z <= 1.0 {
                let point = bounds.min
                    + (bounds.max - bounds.min)
                        * DVec3::new(f64::from(x), f64::from(y), f64::from(z));
                clear += i32::from(shape::ray_trace(level, point, origin, false, false).is_none());
                total += 1;
                z = (f64::from(z) + step.z) as f32;
            }
            y = (f64::from(y) + step.y) as f32;
        }
        x = (f64::from(x) + step.x) as f32;
    }
    clear as f32 / total as f32
}

impl Level {
    /// `World.newExplosion`; a flaming one also sets fire to blocks within its reach.
    /// The creeper that explodes is the attacker; TNT and fireballs have none.
    pub fn explode(&mut self, origin: DVec3, size: f32, attacker: Option<&Entity>, flaming: bool) {
        let source = attacker.map(|entity| entity.id);
        let mut affected = FxHashSet::default();
        for x in 0..16 {
            for y in 0..16 {
                for z in 0..16 {
                    if x != 0 && x != 15 && y != 0 && y != 15 && z != 0 && z != 15 {
                        continue;
                    }
                    let direction = DVec3::from_array(
                        [x, y, z].map(|v| f64::from(v as f32 / 15.0 * 2.0 - 1.0)),
                    )
                    .normalize();
                    let mut strength = size * (0.7 + self.rand.next_float() * 0.6);
                    let mut point = origin;
                    while strength > 0.0 {
                        let [x, y, z] = point.to_array().map(floor_f64);
                        // Outside the world's height there is only air
                        if !self.is_loaded(ChunkPos::containing(x, z)) {
                            break;
                        }
                        let block = BlockView::block(self, x, y, z);
                        if !block.is_air() {
                            strength -= (block.properties().map_or(0.0, |p| p.resistance / 5.0)
                                + 0.3)
                                * 0.3;
                        }
                        if strength > 0.0 {
                            affected.insert([x, y, z]);
                        }
                        point += direction * f64::from(0.3_f32);
                        strength -= 0.3 * (12.0 / 16.0);
                    }
                }
            }
        }
        let radius = f64::from(size * 2.0);
        let victims: Vec<_> = self
            .entities
            .iter()
            .filter(|e| {
                Some(e.entity.id) != source
                    && e.entity.pos.distance_squared(origin) <= radius * radius
            })
            .map(|e| (e.entity.id, e.entity.pos, e.entity.bb))
            .collect();
        for (id, position, bounds) in victims {
            let delta = position - origin;
            let exposure =
                (1.0 - delta.length() / radius) * f64::from(density(self, origin, &bounds));
            self.attack_entity(
                id,
                attacker,
                ((exposure * exposure + exposure) / 2.0 * 8.0 * radius + 1.0) as i32,
            );
            if let Some(stored) = self.entities.get_mut(id) {
                stored.entity.motion += delta.normalize_or_zero() * exposure;
                self.events.push(super::velocity_packet(&stored.entity));
            }
        }
        let mut affected: Vec<_> = affected.into_iter().collect();
        affected.sort_unstable();
        if flaming {
            for &[x, y, z] in affected.iter().rev() {
                if self.block(x, y, z).is_air()
                    && self.block(x, y - 1, z).is_opaque_cube()
                    && self.rand.next_int(3) == 0
                {
                    self.set_block_notify(x, y, z, Block::FIRE);
                }
            }
        }
        let pitch = (1.0 + (self.rand.next_float() - self.rand.next_float()) * 0.2) * 0.7;
        self.events.push(Packet::Sound {
            name: "random.explode".into(),
            pos: origin.to_array(),
            volume: 4.0,
            pitch,
        });
        for &[x, y, z] in &affected {
            let point =
                DVec3::from_array([x, y, z].map(|v| f64::from(v as f32 + self.rand.next_float())));
            let delta = point - origin;
            let speed = 0.5 / (delta.length() / f64::from(size) + 0.1)
                * f64::from(self.rand.next_float() * self.rand.next_float() + 0.3);
            let velocity = delta.normalize_or_zero() * speed;
            self.events.push(Packet::Particle {
                name: "explode".into(),
                pos: ((point + origin) / 2.0).to_array(),
                velocity: velocity.to_array(),
            });
            self.events.push(Packet::Particle {
                name: "smoke".into(),
                pos: point.to_array(),
                velocity: velocity.to_array(),
            });
            let block = BlockView::block(self, x, y, z);
            if block.is_air() {
                continue;
            }
            let metadata = BlockView::metadata(self, x, y, z);
            if block != Block::TNT {
                crate::blocks::drop_with_chance(self, block, [x, y, z], metadata, 0.3);
            }
            self.set_block_notify(x, y, z, Block::AIR);
            if block == Block::TNT {
                self.prime_tnt(DVec3::from_array([x, y, z].map(f64::from)) + 0.5, true);
            }
        }
        self.events.push(Packet::Explosion {
            x: origin.x,
            y: origin.y,
            z: origin.z,
            size,
            blocks: affected
                .into_iter()
                .map(|p| std::array::from_fn(|i| (p[i] - origin[i] as i32) as i8))
                .collect(),
        });
    }
}