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 / player / combat.rs

use common::entity::mob::MobKind;
use common::entity::vehicle::VehicleKind;
use common::inventory::take;
use common::item::{Item, ItemKind, ItemStack};
use common::world::{Block, shape};
use glam::DVec3;

use super::ServerPlayer;
use crate::entity::Kind;
use crate::level::{DroppedItem, Level};

impl ServerPlayer {
    pub(super) fn use_entity(&mut self, level: &mut Level, id: i32, attack: bool) {
        if self.window != 0 || self.died {
            return;
        }
        let Some(source) = self.entity(level).cloned() else {
            return;
        };
        let Some(target) = level.entities.get(id) else {
            return;
        };
        if target.entity.dead
            || (!matches!(
                target.kind,
                Kind::Painting { .. } | Kind::Fireball(_) | Kind::Boat(_) | Kind::Cart(_)
            ) && target.entity.living.as_ref().is_none_or(|l| l.health <= 0))
            || id == source.id
        {
            return;
        }
        let eye = self.eye(level);
        let point = target.entity.pos + DVec3::Y * f64::from(target.entity.eye_height());
        if source.pos.distance_squared(target.entity.pos) >= 36.0
            || shape::ray_trace(level, eye, point, false, false).is_some()
        {
            return;
        }
        if attack {
            let living_target = target.entity.living.is_some();
            let held = self.inventory.held();
            let mut damage = held.map_or(1, |stack| stack.damage_vs_entity());
            if source.motion.y < 0.0 {
                damage += 1;
            }
            level.attack_entity(id, Some(&source), damage);
            if living_target {
                self.send_stat(2020, damage);
            }
            if let Some(target) = level
                .entities
                .get(id)
                .filter(|e| e.entity.living.as_ref().is_some_and(|l| l.health > 0))
                .map(|e| e.entity.clone())
            {
                level.alert_wolves(&source, &self.name, &target, false);
            }
            let wear = match held.and_then(|s| s.item.properties()).map(|p| p.kind) {
                Some(ItemKind::Sword(_)) => 1,
                Some(ItemKind::Tool(..)) => 2,
                _ => 0,
            };
            if living_target {
                if wear > 0
                    && let Some(stack) = held
                {
                    self.used_item(stack);
                }
                self.damage_held(wear);
            }
            // `ItemSaddle.hitEntity` saddles a pig as well
            if held.is_some_and(|stack| stack.item == Item::SADDLE)
                && let Some(target) = level.entities.get_mut(id)
                && let Kind::Mob(mob) = &mut target.kind
                && mob.kind == MobKind::Pig
                && !mob.saddled
            {
                mob.saddled = true;
                let chunk = target.chunk();
                level.mark_entity_chunk(chunk);
                take(self.inventory.held_mut(), 1);
            }
        } else {
            let Some(mut stored) = level.entities.take_for_tick(id) else {
                return;
            };
            if let Kind::Mob(mob) = &mut stored.kind
                && mob.kind == MobKind::Wolf
                && let Some(consume) =
                    level.use_wolf(&mut stored.entity, mob, &self.name, self.inventory.held())
            {
                if consume {
                    take(self.inventory.held_mut(), 1);
                }
                level.mark_entity_chunk(stored.chunk());
                level.entities.finish_tick(stored);
                return self.sync_container(level);
            }
            let rider = stored.entity.rider;
            // Vehicles only refuse another player; a mob passenger is thrown out
            let seat_free = rider.is_none_or(|rider| {
                rider == self.entity_id
                    || !level
                        .entities
                        .get(rider)
                        .is_some_and(|e| matches!(e.kind, Kind::Player { .. }))
            });
            let saddle_free = rider.is_none_or(|rider| rider == self.entity_id);
            let ride = match &mut stored.kind {
                Kind::Cart(cart) => {
                    let kind = cart.kind;
                    if kind == VehicleKind::PoweredCart {
                        if self.inventory.held().is_some_and(|s| s.item == Item::COAL) {
                            take(self.inventory.held_mut(), 1);
                            cart.fuel += 1200;
                        }
                        cart.push = (stored.entity.pos - source.pos).with_y(0.0);
                        level.mark_entity_chunk(stored.chunk());
                    }
                    kind == VehicleKind::Minecart && seat_free
                }
                Kind::Boat(_) => seat_free,
                Kind::Mob(mob) if mob.kind == MobKind::Pig && mob.saddled && saddle_free => true,
                kind => {
                    if let Kind::Mob(mob) = kind
                        && let Some(held) = self.inventory.held()
                    {
                        match (mob.kind, held.item) {
                            (MobKind::Cow, Item::BUCKET) => {
                                *self.inventory.held_mut() = Some(ItemStack::one(Item::MILK_BUCKET))
                            }
                            (MobKind::Sheep, Item::SHEARS) if !mob.sheared => {
                                mob.sheared = true;
                                for _ in 0..2 + stored.entity.rand.next_int(3) {
                                    // `EntitySheep.interact` pushes each wool item a little further
                                    let random = &mut stored.entity.rand;
                                    let lift = f64::from(random.next_float() * 0.05);
                                    let mut spread = || {
                                        f64::from((random.next_float() - random.next_float()) * 0.1)
                                    };
                                    let impulse = DVec3::new(spread(), lift, spread());
                                    let pos = stored.entity.pos + DVec3::Y;
                                    let wool = Item::from_block(Block::WOOL);
                                    level.dropped_items.push(DroppedItem {
                                        x: pos.x,
                                        y: pos.y,
                                        z: pos.z,
                                        stack: ItemStack::new(wool, 1, i32::from(mob.color)),
                                        motion: None,
                                        impulse,
                                        pickup_delay: 10,
                                    });
                                }
                                self.damage_held(1);
                            }
                            (MobKind::Sheep, Item::DYE)
                                if !mob.sheared && mob.color != (!held.damage as u8 & 15) =>
                            {
                                mob.color = !held.damage as u8 & 15;
                                take(self.inventory.held_mut(), 1);
                            }
                            (MobKind::Pig, Item::SADDLE) if !mob.saddled => {
                                mob.saddled = true;
                                take(self.inventory.held_mut(), 1);
                            }
                            _ => {}
                        }
                    }
                    // The client asks for the generic item use separately
                    level.mark_entity_chunk(stored.chunk());
                    false
                }
            };
            let storage =
                matches!(&stored.kind, Kind::Cart(cart) if cart.kind == VehicleKind::StorageCart);
            level.entities.finish_tick(stored);
            if storage {
                self.open_cart(level, id);
            } else if ride {
                self.mount(level, Some(id));
            }
        }
        self.sync_container(level);
    }
}