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