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 / common / src / inventory / mod.rs

mod container;
mod furnace;

pub use container::{ClickResult, Container, ContainerKind};
pub use furnace::{smelting_result, tick_furnace};

use crate::item::{ItemKind, ItemStack, slot_tags};
use crate::nbt::Compound;
use crate::world::{Block, Material};

#[derive(Clone, Debug, PartialEq)]
pub struct PlayerInventory {
    pub main: [Option<ItemStack>; 36],
    pub armor: [Option<ItemStack>; 4],
    pub selected: usize,
}

impl Default for PlayerInventory {
    fn default() -> Self {
        Self {
            main: [None; 36],
            armor: [None; 4],
            selected: 0,
        }
    }
}

impl PlayerInventory {
    pub fn held(&self) -> Option<ItemStack> {
        self.main.get(self.selected).copied().flatten()
    }

    pub fn held_mut(&mut self) -> &mut Option<ItemStack> {
        &mut self.main[self.selected.min(8)]
    }

    pub fn read(nbt: &Compound) -> Self {
        let mut inventory = Self::default();
        for entry in nbt.compounds("Inventory") {
            let stack = ItemStack::read_nbt(entry);
            if stack.count <= 0 {
                continue;
            }
            let slot = entry.byte("Slot") as u8;
            match slot {
                0..=35 => inventory.main[usize::from(slot)] = Some(stack),
                100..=103 => inventory.armor[usize::from(slot - 100)] = Some(stack),
                _ => {}
            }
        }
        inventory
    }

    pub fn write(&self, nbt: &mut Compound) {
        let armor = self
            .armor
            .iter()
            .enumerate()
            .map(|(i, stack)| (100 + i, stack));
        let slots = self.main.iter().enumerate().chain(armor);
        nbt.set_list("Inventory", slot_tags(slots));
    }

    pub fn insert(&mut self, mut stack: ItemStack) -> Option<ItemStack> {
        for empty in [false, true] {
            for slot in &mut self.main {
                if slot.is_none() == empty {
                    merge(slot, &mut stack, 64);
                    if stack.count == 0 {
                        return None;
                    }
                }
            }
        }
        Some(stack)
    }

    pub fn can_harvest(&self, block: Block) -> bool {
        !matches!(
            block.material(),
            Material::Rock | Material::Iron | Material::Snow | Material::BuiltSnow | Material::Web
        ) || self.held().is_some_and(|item| item.can_harvest(block))
    }

    pub fn mining_progress(&self, block: Block, underwater: bool, on_ground: bool) -> f32 {
        let Some(properties) = block.properties() else {
            return 0.0;
        };
        if properties.hardness < 0.0 {
            return 0.0;
        }
        if !self.can_harvest(block) {
            return 1.0 / properties.hardness / 100.0;
        }
        let mut strength = self.held().map_or(1.0, |item| item.strength_against(block));
        if underwater {
            strength /= 5.0;
        }
        if !on_ground {
            strength /= 5.0;
        }
        strength / properties.hardness / 30.0
    }

    pub fn damage_held(&mut self, amount: i32) {
        wear(self.held_mut(), amount);
    }

    pub fn armor_value(&self) -> i32 {
        armor_value(&self.armor)
    }
}

/// `InventoryPlayer.getTotalArmorValue`: protection scaled by the remaining durability.
pub fn armor_value(armor: &[Option<ItemStack>]) -> i32 {
    let (mut protection, mut remaining, mut total) = (0, 0, 0);
    for stack in armor.iter().flatten() {
        if let Some(ItemKind::Armor { slot, .. }) = stack.item.properties().map(|p| p.kind) {
            protection += [3, 8, 6, 3][usize::from(slot)];
            total += stack.max_damage();
            remaining += (stack.max_damage() - stack.damage).max(0);
        }
    }
    if total == 0 {
        0
    } else {
        (protection - 1) * remaining / total + 1
    }
}

/// `InventoryPlayer.damageArmor`: wears every piece, removing broken ones.
pub fn damage_armor(armor: &mut [Option<ItemStack>], amount: i32) {
    for slot in armor {
        if slot.is_some_and(|stack| {
            matches!(
                stack.item.properties().map(|p| p.kind),
                Some(ItemKind::Armor { .. })
            )
        }) {
            wear(slot, amount);
        }
    }
}

/// `damageItem`, removing the stack once it broke.
fn wear(slot: &mut Option<ItemStack>, amount: i32) {
    if let Some(stack) = slot {
        stack.damage_item(amount);
        if stack.count <= 0 {
            *slot = None;
        }
    }
}

pub fn compatible(a: ItemStack, b: ItemStack) -> bool {
    a.item == b.item
        && (!a.has_subtypes() || a.damage == b.damage)
        && a.is_stackable()
        && b.is_stackable()
}

pub fn take(slot: &mut Option<ItemStack>, count: i32) -> Option<ItemStack> {
    let stack = slot.as_mut()?;
    if count <= 0 {
        return None;
    }
    let taken = stack.split(count.min(stack.count));
    if stack.count <= 0 {
        *slot = None;
    }
    Some(taken)
}

fn merge(slot: &mut Option<ItemStack>, stack: &mut ItemStack, limit: i32) {
    let count = match *slot {
        None => stack.count.min(stack.max_stack()).min(limit),
        Some(other) if compatible(other, *stack) => stack
            .count
            .min((other.max_stack().min(limit) - other.count).max(0)),
        _ => 0,
    };
    if count <= 0 {
        return;
    }
    match slot {
        Some(other) => other.count += count,
        None => *slot = Some(ItemStack::new(stack.item, count, stack.damage)),
    }
    stack.count -= count;
}

fn armor_accepts(index: usize, stack: ItemStack) -> bool {
    stack.item.properties().is_some_and(
        |p| matches!(p.kind, ItemKind::Armor { slot, .. } if usize::from(slot) == 3 - index),
    ) || index == 3 && stack.item.block() == Some(Block::PUMPKIN)
}