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 container.rs
use super::{PlayerInventory, armor_accepts, compatible, merge, take};
use crate::crafting::{self, Grid};
use crate::item::ItemStack;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ContainerKind {
Inventory,
Workbench,
Chest { slots: usize },
Furnace,
Dispenser,
}
impl ContainerKind {
pub fn inventory_start(self) -> usize {
match self {
Self::Inventory => 9,
Self::Workbench => 10,
_ => self.external_count(),
}
}
pub fn slot_count(self) -> usize {
self.inventory_start() + 36
}
pub fn external_count(self) -> usize {
match self {
Self::Chest { slots } => slots,
Self::Furnace => 3,
Self::Dispenser => 9,
_ => 0,
}
}
}
#[derive(Clone, Debug)]
pub struct Container {
pub kind: ContainerKind,
pub craft: [Option<ItemStack>; 9],
pub carried: Option<ItemStack>,
}
#[derive(Default, Debug)]
pub struct ClickResult {
pub item: Option<ItemStack>,
pub dropped: Option<ItemStack>,
pub crafted: Option<ItemStack>,
}
#[derive(Clone, Copy)]
enum Slot {
Result,
Craft(usize),
Armor(usize),
Main(usize),
External(usize),
}
impl Container {
pub fn new(kind: ContainerKind) -> Self {
Self {
kind,
craft: [None; 9],
carried: None,
}
}
fn slot(&self, index: usize) -> Option<Slot> {
let base = self.kind.inventory_start();
if index >= self.kind.slot_count() {
return None;
}
if index >= base {
let index = index - base;
return Some(Slot::Main(if index < 27 { index + 9 } else { index - 27 }));
}
Some(match self.kind {
ContainerKind::Inventory if index == 0 => Slot::Result,
ContainerKind::Inventory if index <= 4 => Slot::Craft(index - 1),
ContainerKind::Inventory => Slot::Armor(8 - index),
ContainerKind::Workbench if index == 0 => Slot::Result,
ContainerKind::Workbench => Slot::Craft(index - 1),
_ => Slot::External(index),
})
}
pub fn result(&self) -> Option<ItemStack> {
let width = match self.kind {
ContainerKind::Inventory => 2,
ContainerKind::Workbench => 3,
_ => return None,
};
crafting::find(&Grid {
width,
slots: &self.craft[..width * width],
})
}
pub fn get(
&self,
inventory: &PlayerInventory,
external: &[Option<ItemStack>],
index: usize,
) -> Option<ItemStack> {
match self.slot(index)? {
Slot::Result => self.result(),
Slot::Craft(i) => self.craft[i],
Slot::Armor(i) => inventory.armor[i],
Slot::Main(i) => inventory.main[i],
Slot::External(i) => external.get(i).copied().flatten(),
}
}
pub fn set(
&mut self,
inventory: &mut PlayerInventory,
external: &mut [Option<ItemStack>],
index: usize,
value: Option<ItemStack>,
) {
match self.slot(index) {
Some(Slot::Craft(i)) => self.craft[i] = value,
Some(Slot::Armor(i)) => inventory.armor[i] = value,
Some(Slot::Main(i)) => inventory.main[i] = value,
Some(Slot::External(i)) if i < external.len() => external[i] = value,
_ => {}
}
}
pub fn contents(
&self,
inventory: &PlayerInventory,
external: &[Option<ItemStack>],
) -> Vec<Option<ItemStack>> {
(0..self.kind.slot_count())
.map(|i| self.get(inventory, external, i))
.collect()
}
fn accepts(&self, index: usize, stack: ItemStack) -> bool {
match self.slot(index) {
None | Some(Slot::Result) => false,
Some(Slot::Armor(i)) => armor_accepts(i, stack),
Some(Slot::External(2)) if self.kind == ContainerKind::Furnace => false,
_ => true,
}
}
fn limit(&self, index: usize) -> i32 {
if matches!(self.slot(index), Some(Slot::Armor(_))) {
1
} else {
64
}
}
fn consume_recipe(&mut self) {
for slot in &mut self.craft {
if let Some(ingredient) = take(slot, 1)
&& let Some(container) = ingredient.item.properties().and_then(|p| p.container)
{
*slot = Some(ItemStack::one(container));
}
}
}
pub fn click(
&mut self,
inventory: &mut PlayerInventory,
external: &mut [Option<ItemStack>],
index: i16,
button: i8,
shift: bool,
) -> ClickResult {
if !(0..=1).contains(&button) {
return ClickResult::default();
}
if index == -999 {
let dropped = take(&mut self.carried, if button == 0 { i32::MAX } else { 1 });
return ClickResult {
dropped,
..ClickResult::default()
};
}
let Ok(index) = usize::try_from(index) else {
return ClickResult::default();
};
let Some(slot) = self.slot(index) else {
return ClickResult::default();
};
let before = self.get(inventory, external, index);
let crafting_slot = matches!(slot, Slot::Result);
if shift {
let item = self.transfer(inventory, external, index);
return ClickResult {
item,
crafted: item.filter(|_| crafting_slot),
..ClickResult::default()
};
}
if crafting_slot {
let crafted = before.filter(|crafted| {
self.carried.is_none_or(|held| {
compatible(held, *crafted) && held.count + crafted.count <= held.max_stack()
})
});
if let Some(crafted) = crafted {
let count = self.carried.map_or(0, |held| held.count) + crafted.count;
self.carried = Some(ItemStack {
count,
..self.carried.unwrap_or(crafted)
});
self.consume_recipe();
}
return ClickResult {
item: before,
crafted,
..ClickResult::default()
};
}
let mut value = before;
match (value, self.carried) {
(Some(stack), None) => {
self.carried = take(
&mut value,
if button == 0 {
stack.count
} else {
(stack.count + 1) / 2
},
)
}
(_, Some(mut held)) if self.accepts(index, held) => {
if value.is_none_or(|other| compatible(other, held)) {
let count = if button == 0 { held.count } else { 1 };
let mut part = ItemStack::new(held.item, count, held.damage);
merge(&mut value, &mut part, self.limit(index));
held.count -= count - part.count;
self.carried = (held.count > 0).then_some(held);
} else if held.count <= self.limit(index).min(held.max_stack()) {
self.carried = value;
value = Some(held);
}
}
(Some(stack), Some(mut held))
if compatible(stack, held) && stack.count + held.count <= held.max_stack() =>
{
held.count += stack.count;
self.carried = Some(held);
value = None;
}
_ => {}
}
self.set(inventory, external, index, value);
ClickResult {
item: before,
..ClickResult::default()
}
}
fn transfer(
&mut self,
inventory: &mut PlayerInventory,
external: &mut [Option<ItemStack>],
index: usize,
) -> Option<ItemStack> {
let original = self.get(inventory, external, index)?;
// `ContainerDispenser` keeps the base `getStackInSlot`, which moves nothing
if self.kind == ContainerKind::Dispenser {
return Some(original);
}
let mut stack = original;
let base = self.kind.inventory_start();
let (start, end, reverse) = if index < base {
(
base,
base + 36,
matches!(self.slot(index), Some(Slot::Result))
|| matches!(self.kind, ContainerKind::Chest { .. })
|| index == 2 && self.kind == ContainerKind::Furnace,
)
} else if matches!(self.kind, ContainerKind::Chest { .. }) {
(0, base, false)
} else if index < base + 27 {
(base + 27, base + 36, false)
} else {
(base, base + 27, false)
};
let crafting = matches!(self.slot(index), Some(Slot::Result));
// Crafting transfers commit only when the complete output fits
let mut changes = Vec::new();
for empty in [false, true] {
for offset in 0..end - start {
let target = if reverse {
end - 1 - offset
} else {
start + offset
};
if !self.accepts(target, stack) {
continue;
}
let mut value = self.get(inventory, external, target);
if value.is_none() != empty {
continue;
}
let before = value;
merge(&mut value, &mut stack, self.limit(target));
if value != before {
changes.push((target, value));
}
if stack.count == 0 {
break;
}
}
if stack.count == 0 {
break;
}
}
if stack.count == original.count || crafting && stack.count != 0 {
return None;
}
for (target, value) in changes {
self.set(inventory, external, target, value);
}
if crafting {
self.consume_recipe();
} else {
self.set(
inventory,
external,
index,
(stack.count > 0).then_some(stack),
);
}
Some(original)
}
pub fn close(&mut self) -> Vec<ItemStack> {
let mut drops: Vec<_> = self.carried.take().into_iter().collect();
drops.extend(self.craft.iter_mut().filter_map(Option::take));
drops
}
}