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

//! `EntityLightningBolt`: kept apart from other entities like `World.weatherEffects`.

use common::JavaRandom;
use common::entity::mob::MobKind;
use common::math::floor_f64;
use common::physics::Aabb;
use common::protocol::Packet;
use common::world::Block;
use glam::DVec3;

use super::Kind;
use crate::level::Level;

pub struct Lightning {
    pub id: i32,
    pub pos: DVec3,
    rand: JavaRandom,
    /// `lightningState`: 2 when it strikes, counting down between flashes.
    state: i32,
    /// `boltLivingTime`: flashes left.
    flashes: i32,
}

impl Level {
    /// Ignites the block at `position` if fire can burn there.
    fn ignite(&mut self, [x, y, z]: [i32; 3]) {
        if self.is_air(x, y, z) && crate::blocks::fire_fits(self, x, y, z) {
            self.set_block_notify(x, y, z, Block::FIRE);
        }
    }

    /// `World.addLightningBolt` with the `EntityLightningBolt` constructor: on hard difficulty
    /// and up it sets fire to the ground around it.
    pub fn strike_lightning(&mut self, pos: DVec3) {
        let id = self.entities.allocate();
        let mut rand = self.entities.random();
        rand.next_long();
        let flashes = rand.next_int(3) + 1;
        let [x, y, z] = pos.to_array().map(floor_f64);
        if self.difficulty >= 2 && self.chunks_exist_around(x, y, z, 10) {
            self.ignite([x, y, z]);
            for _ in 0..4 {
                let spot = [x, y, z].map(|v| v + rand.next_int(3) - 1);
                self.ignite(spot);
            }
        }
        let [x, y, z] = pos.to_array().map(|v| floor_f64(v * 32.0));
        self.events.push(Packet::Weather {
            entity: id,
            lightning: true,
            x,
            y,
            z,
        });
        self.lightning.push(Lightning {
            id,
            pos,
            rand,
            state: 2,
            flashes,
        });
    }

    /// `EntityLightningBolt.onUpdate` for every bolt; their sounds are the clients'.
    pub(super) fn tick_lightning(&mut self) {
        let mut bolts = std::mem::take(&mut self.lightning);
        bolts.retain_mut(|bolt| {
            bolt.state -= 1;
            if bolt.state < 0 {
                if bolt.flashes == 0 {
                    return false;
                }
                if bolt.state < -bolt.rand.next_int(10) {
                    bolt.flashes -= 1;
                    bolt.state = 1;
                    bolt.rand.next_long();
                    let [x, y, z] = bolt.pos.to_array().map(floor_f64);
                    if self.chunks_exist_around(x, y, z, 10) {
                        self.ignite([x, y, z]);
                    }
                }
            }
            if bolt.state >= 0 {
                self.strike_entities(bolt.pos);
            }
            true
        });
        bolts.append(&mut self.lightning);
        self.lightning = bolts;
    }

    /// `Entity.onStruckByLightning` for everything within three blocks.
    fn strike_entities(&mut self, pos: DVec3) {
        let area = Aabb::new(pos, pos + DVec3::Y * 6.0).inflate(DVec3::splat(3.0));
        let ids: Vec<i32> = self
            .entities
            .iter()
            .filter(|e| e.entity.bb.intersects(&area))
            .map(|e| e.entity.id)
            .collect();
        for id in ids {
            let Some(stored) = self.entities.get_mut(id) else {
                continue;
            };
            // `EntityPig.onStruckByLightning`
            if matches!(&stored.kind, Kind::Mob(mob) if mob.kind == MobKind::Pig) {
                if stored.entity.dead {
                    continue;
                }
                stored.entity.dead = true;
                let (pos, yaw, pitch) = (stored.entity.pos, stored.entity.yaw, stored.entity.pitch);
                let mut pigman = self.mob_entity(MobKind::PigZombie, pos, yaw);
                pigman.entity.set_location_and_angles(pos, yaw, pitch);
                self.spawn(pigman);
                continue;
            }
            if !stored.entity.immune_to_fire {
                self.attack_entity(id, None, 5);
            }
            let Some(stored) = self.entities.get_mut(id) else {
                continue;
            };
            let entity = &mut stored.entity;
            entity.fire += 1;
            if entity.fire == 0 {
                entity.fire = 300;
            }
            if let Kind::Mob(mob) = &mut stored.kind
                && mob.kind == MobKind::Creeper
            {
                mob.powered = true;
            }
        }
    }
}