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 crafting.rs
//! Port of `CraftingManager` with its shaped and shapeless recipes.
use std::cmp::Reverse;
use std::sync::LazyLock;
use crate::item::{Item, ItemKind, ItemStack, ToolClass::*, ToolMaterial::*};
use crate::world::Block;
/// Damage that matches any ingredient damage.
const ANY: i32 = -1;
/// A recipe ingredient as the original stores it: a block matches any damage, an item only damage 0.
#[derive(Clone, Copy)]
enum Ingredient {
Block(Block),
Item(Item),
Stack(Item, i32),
}
impl Ingredient {
/// The stack the original recipe holds for this ingredient.
fn stack(self, shaped: bool) -> ItemStack {
match self {
Self::Block(block) => {
ItemStack::new(Item::from_block(block), 1, if shaped { ANY } else { 0 })
}
Self::Item(item) => ItemStack::new(item, 1, 0),
Self::Stack(item, damage) => ItemStack::new(item, 1, damage),
}
}
}
fn matches(pattern: &ItemStack, stack: &ItemStack) -> bool {
pattern.item == stack.item && (pattern.damage == ANY || pattern.damage == stack.damage)
}
struct Recipe {
/// Width and height of a shaped recipe, `None` for a shapeless one.
shape: Option<(usize, usize)>,
items: Vec<Option<ItemStack>>,
output: ItemStack,
}
/// A crafting grid: `width` columns, row-major.
pub struct Grid<'a> {
pub width: usize,
pub slots: &'a [Option<ItemStack>],
}
impl Grid<'_> {
/// `InventoryCrafting.getStackInRowAndColumn`.
fn at(&self, x: usize, y: usize) -> Option<&ItemStack> {
if x >= self.width {
return None;
}
self.slots.get(x + y * self.width)?.as_ref()
}
}
impl Recipe {
fn matches(&self, grid: &Grid) -> bool {
let cells = || (0..3).flat_map(|y| (0..3).map(move |x| (x, y)));
let Some((width, height)) = self.shape else {
let mut remaining: Vec<_> = self.items.iter().flatten().collect();
for stack in cells().filter_map(|(x, y)| grid.at(x, y)) {
match remaining.iter().position(|pattern| matches(pattern, stack)) {
Some(i) => remaining.remove(i),
None => return false,
};
}
return remaining.is_empty();
};
let fits = |ox: usize, oy: usize, flipped: bool| {
cells().all(|(x, y)| {
let inside = x >= ox && y >= oy && x - ox < width && y - oy < height;
let expected = inside.then(|| {
let rx = if flipped { width + ox - 1 - x } else { x - ox };
self.items[rx + (y - oy) * width]
});
match (expected.flatten(), grid.at(x, y)) {
(None, None) => true,
(Some(pattern), Some(stack)) => matches(&pattern, stack),
_ => false,
}
})
};
(0..=3 - width)
.any(|ox| (0..=3 - height).any(|oy| fits(ox, oy, true) || fits(ox, oy, false)))
}
}
/// `CraftingManager.findMatchingRecipe`.
pub fn find(grid: &Grid) -> Option<ItemStack> {
RECIPES
.iter()
.find(|recipe| recipe.matches(grid))
.map(|recipe| recipe.output)
}
struct Builder(Vec<Recipe>);
impl Builder {
fn shaped(&mut self, output: ItemStack, rows: &[&str], keys: &[(char, Ingredient)]) {
let key = |c| {
keys.iter()
.find(|(k, _)| *k == c)
.map(|(_, i)| i.stack(true))
};
self.0.push(Recipe {
shape: Some((rows.last().map_or(0, |row| row.len()), rows.len())),
items: rows.concat().chars().map(key).collect(),
output,
});
}
fn shapeless(&mut self, output: ItemStack, ingredients: &[Ingredient]) {
self.0.push(Recipe {
shape: None,
items: ingredients.iter().map(|i| Some(i.stack(false))).collect(),
output,
});
}
}
/// `r.shaped` with the key list written like the original: `'#' => ingredient`.
macro_rules! shaped {
($r:ident, $output:expr, $rows:expr, $($key:literal => $ingredient:expr),+) => {
$r.shaped($output, $rows, &[$(($key, $ingredient)),+])
};
}
/// The item of the item table with this kind.
fn item_of(kind: ItemKind) -> Item {
(256..i16::MAX)
.map(Item)
.find(|item| item.properties().is_some_and(|p| p.kind == kind))
.expect("item kind in the item table")
}
static RECIPES: LazyLock<Vec<Recipe>> = LazyLock::new(|| {
use Ingredient::{Block as B, Item as I, Stack as S};
let mut r = Builder(Vec::new());
let stack = |item: Item, count: i32| ItemStack::new(item, count, 0);
let block = |block: Block, count: i32| ItemStack::new(Item::from_block(block), count, 0);
let (planks, cobble, stone) = (B(Block::PLANKS), B(Block::COBBLESTONE), B(Block::STONE));
let (stick, iron, redstone) = (I(Item::STICK), I(Item::IRON_INGOT), I(Item::REDSTONE));
let tiers = [Wood, Stone, Iron, Diamond, Gold];
let materials = [planks, cobble, iron, I(Item::DIAMOND), I(Item::GOLD_INGOT)];
// RecipesTools
for (material, tier) in materials.into_iter().zip(tiers) {
for (rows, kind) in [
(&["XXX", " # ", " # "][..], ItemKind::Tool(Pickaxe, tier)),
(&["X", "#", "#"], ItemKind::Tool(Shovel, tier)),
(&["XX", "X#", " #"], ItemKind::Tool(Axe, tier)),
(&["XX", " #", " #"], ItemKind::Hoe(tier)),
] {
shaped!(r, stack(item_of(kind), 1), rows, '#' => stick, 'X' => material);
}
}
shaped!(r, stack(Item::SHEARS, 1), &[" #", "# "], '#' => iron);
// RecipesWeapons
for (material, tier) in materials.into_iter().zip(tiers) {
let sword = item_of(ItemKind::Sword(tier));
shaped!(r, stack(sword, 1), &["X", "X", "#"], '#' => stick, 'X' => material);
}
shaped!(r, stack(Item::BOW, 1), &[" #X", "# X", " #X"], 'X' => I(Item::STRING), '#' => stick);
shaped!(r, stack(Item::ARROW, 4), &["X", "#", "Y"],
'Y' => I(Item::FEATHER), 'X' => I(Item::FLINT), '#' => stick);
// RecipesIngots
for (storage, ingot) in [
(Block::GOLD_BLOCK, ItemStack::new(Item::GOLD_INGOT, 9, 0)),
(Block::IRON_BLOCK, ItemStack::new(Item::IRON_INGOT, 9, 0)),
(Block::DIAMOND_BLOCK, ItemStack::new(Item::DIAMOND, 9, 0)),
(Block::LAPIS_BLOCK, ItemStack::new(Item::DYE, 9, 4)),
] {
shaped!(r, block(storage, 1), &["###", "###", "###"], '#' => S(ingot.item, ingot.damage));
shaped!(r, ingot, &["#"], '#' => B(storage));
}
// RecipesFood
for (x, y) in [
(Block::BROWN_MUSHROOM, Block::RED_MUSHROOM),
(Block::RED_MUSHROOM, Block::BROWN_MUSHROOM),
] {
shaped!(r, stack(Item::MUSHROOM_STEW, 1), &["Y", "X", "#"],
'X' => B(x), 'Y' => B(y), '#' => I(Item::BOWL));
}
shaped!(r, stack(Item::COOKIE, 8), &["#X#"], 'X' => S(Item::DYE, 3), '#' => I(Item::WHEAT));
// RecipesCrafting
shaped!(r, block(Block::CHEST, 1), &["###", "# #", "###"], '#' => planks);
shaped!(r, block(Block::FURNACE, 1), &["###", "# #", "###"], '#' => cobble);
shaped!(r, block(Block::CRAFTING_TABLE, 1), &["##", "##"], '#' => planks);
shaped!(r, block(Block::SANDSTONE, 1), &["##", "##"], '#' => B(Block::SAND));
// RecipesArmor
let materials = [
I(Item::LEATHER),
B(Block::FIRE),
iron,
I(Item::DIAMOND),
I(Item::GOLD_INGOT),
];
for (material_index, material) in (0..).zip(materials) {
for (slot, rows) in (0..).zip([
&["XXX", "X X"][..],
&["X X", "XXX", "XXX"],
&["XXX", "X X", "X X"],
&["X X", "X X"],
]) {
// `ItemArmor` ids run from helmet to boots, material by material
let armor = Item(Item::LEATHER_HELMET.0 + 4 * material_index + slot);
shaped!(r, stack(armor, 1), rows, 'X' => material);
}
}
// RecipesDyes
let dye = |damage: i32| S(Item::DYE, damage);
let wool = Item::from_block(Block::WOOL);
for color in 0..16 {
r.shapeless(
ItemStack::new(wool, 1, !color & 15),
&[dye(color), S(wool, 0)],
);
}
r.shapeless(ItemStack::new(Item::DYE, 2, 11), &[B(Block::DANDELION)]);
r.shapeless(ItemStack::new(Item::DYE, 2, 1), &[B(Block::ROSE)]);
r.shapeless(ItemStack::new(Item::DYE, 3, 15), &[I(Item::BONE)]);
for (count, damage, inputs) in [
(2, 9, &[1, 15][..]),
(2, 14, &[1, 11]),
(2, 10, &[2, 15]),
(2, 8, &[0, 15]),
(2, 7, &[8, 15]),
(3, 7, &[0, 15, 15]),
(2, 12, &[4, 15]),
(2, 6, &[4, 2]),
(2, 5, &[4, 1]),
(2, 13, &[5, 9]),
(3, 13, &[4, 1, 9]),
(4, 13, &[4, 1, 1, 15]),
] {
let ingredients: Vec<Ingredient> = inputs.iter().map(|&d| dye(d)).collect();
r.shapeless(ItemStack::new(Item::DYE, count, damage), &ingredients);
}
// CraftingManager
shaped!(r, stack(Item::PAPER, 3), &["###"], '#' => I(Item::SUGAR_CANE));
shaped!(r, stack(Item::BOOK, 1), &["#", "#", "#"], '#' => I(Item::PAPER));
shaped!(r, block(Block::FENCE, 2), &["###", "###"], '#' => stick);
shaped!(r, block(Block::JUKEBOX, 1), &["###", "#X#", "###"],
'#' => planks, 'X' => I(Item::DIAMOND));
shaped!(r, block(Block::NOTE_BLOCK, 1), &["###", "#X#", "###"], '#' => planks, 'X' => redstone);
shaped!(r, block(Block::BOOKSHELF, 1), &["###", "XXX", "###"],
'#' => planks, 'X' => I(Item::BOOK));
shaped!(r, block(Block::SNOW, 1), &["##", "##"], '#' => I(Item::SNOWBALL));
shaped!(r, block(Block::CLAY, 1), &["##", "##"], '#' => I(Item::CLAY_BALL));
shaped!(r, block(Block::BRICKS, 1), &["##", "##"], '#' => I(Item::BRICK));
shaped!(r, block(Block::GLOWSTONE, 1), &["##", "##"], '#' => I(Item::GLOWSTONE_DUST));
shaped!(r, block(Block::WOOL, 1), &["##", "##"], '#' => I(Item::STRING));
shaped!(r, block(Block::TNT, 1), &["X#X", "#X#", "X#X"],
'X' => I(Item::GUNPOWDER), '#' => B(Block::SAND));
for (damage, material) in [
(3, cobble),
(0, stone),
(1, B(Block::SANDSTONE)),
(2, planks),
] {
let slab = ItemStack::new(Item::from_block(Block::SLAB), 3, damage);
shaped!(r, slab, &["###"], '#' => material);
}
shaped!(r, block(Block::LADDER, 2), &["# #", "###", "# #"], '#' => stick);
shaped!(r, stack(Item::WOODEN_DOOR, 1), &["##", "##", "##"], '#' => planks);
shaped!(r, block(Block::TRAPDOOR, 2), &["###", "###"], '#' => planks);
shaped!(r, stack(Item::IRON_DOOR, 1), &["##", "##", "##"], '#' => iron);
shaped!(r, stack(Item::SIGN, 1), &["###", "###", " X "], '#' => planks, 'X' => stick);
shaped!(r, stack(Item::CAKE, 1), &["AAA", "BEB", "CCC"],
'A' => I(Item::MILK_BUCKET), 'B' => I(Item::SUGAR),
'C' => I(Item::WHEAT), 'E' => I(Item::EGG));
shaped!(r, stack(Item::SUGAR, 1), &["#"], '#' => I(Item::SUGAR_CANE));
shaped!(r, block(Block::PLANKS, 4), &["#"], '#' => B(Block::LOG));
shaped!(r, stack(Item::STICK, 4), &["#", "#"], '#' => planks);
shaped!(r, block(Block::TORCH, 4), &["X", "#"], 'X' => I(Item::COAL), '#' => stick);
shaped!(r, block(Block::TORCH, 4), &["X", "#"], 'X' => S(Item::COAL, 1), '#' => stick);
shaped!(r, stack(Item::BOWL, 4), &["# #", " # "], '#' => planks);
shaped!(r, block(Block::RAIL, 16), &["X X", "X#X", "X X"], 'X' => iron, '#' => stick);
shaped!(r, block(Block::POWERED_RAIL, 6), &["X X", "X#X", "XRX"],
'X' => I(Item::GOLD_INGOT), 'R' => redstone, '#' => stick);
shaped!(r, block(Block::DETECTOR_RAIL, 6), &["X X", "X#X", "XRX"],
'X' => iron, 'R' => redstone, '#' => B(Block::STONE_PRESSURE_PLATE));
shaped!(r, stack(Item::MINECART, 1), &["# #", "###"], '#' => iron);
shaped!(r, block(Block::JACK_O_LANTERN, 1), &["A", "B"],
'A' => B(Block::PUMPKIN), 'B' => B(Block::TORCH));
shaped!(r, stack(Item::CHEST_MINECART, 1), &["A", "B"],
'A' => B(Block::CHEST), 'B' => I(Item::MINECART));
shaped!(r, stack(Item::FURNACE_MINECART, 1), &["A", "B"],
'A' => B(Block::FURNACE), 'B' => I(Item::MINECART));
shaped!(r, stack(Item::BOAT, 1), &["# #", "###"], '#' => planks);
shaped!(r, stack(Item::BUCKET, 1), &["# #", " # "], '#' => iron);
shaped!(r, stack(Item::FLINT_AND_STEEL, 1), &["A ", " B"], 'A' => iron, 'B' => I(Item::FLINT));
shaped!(r, stack(Item::BREAD, 1), &["###"], '#' => I(Item::WHEAT));
shaped!(r, block(Block::OAK_STAIRS, 4), &["# ", "## ", "###"], '#' => planks);
shaped!(r, stack(Item::FISHING_ROD, 1), &[" #", " #X", "# X"],
'#' => stick, 'X' => I(Item::STRING));
shaped!(r, block(Block::COBBLESTONE_STAIRS, 4), &["# ", "## ", "###"], '#' => cobble);
shaped!(r, stack(Item::PAINTING, 1), &["###", "#X#", "###"],
'#' => stick, 'X' => B(Block::WOOL));
shaped!(r, stack(Item::GOLDEN_APPLE, 1), &["###", "#X#", "###"],
'#' => B(Block::GOLD_BLOCK), 'X' => I(Item::APPLE));
shaped!(r, block(Block::LEVER, 1), &["X", "#"], '#' => cobble, 'X' => stick);
shaped!(r, block(Block::REDSTONE_TORCH, 1), &["X", "#"], '#' => stick, 'X' => redstone);
shaped!(r, stack(Item::REPEATER, 1), &["#X#", "III"],
'#' => B(Block::REDSTONE_TORCH), 'X' => redstone, 'I' => stone);
shaped!(r, stack(Item::CLOCK, 1), &[" # ", "#X#", " # "],
'#' => I(Item::GOLD_INGOT), 'X' => redstone);
shaped!(r, stack(Item::COMPASS, 1), &[" # ", "#X#", " # "], '#' => iron, 'X' => redstone);
shaped!(r, stack(Item::MAP, 1), &["###", "#X#", "###"],
'#' => I(Item::PAPER), 'X' => I(Item::COMPASS));
shaped!(r, block(Block::BUTTON, 1), &["#", "#"], '#' => stone);
shaped!(r, block(Block::STONE_PRESSURE_PLATE, 1), &["##"], '#' => stone);
shaped!(r, block(Block::WOODEN_PRESSURE_PLATE, 1), &["##"], '#' => planks);
shaped!(r, block(Block::DISPENSER, 1), &["###", "#X#", "#R#"],
'#' => cobble, 'X' => I(Item::BOW), 'R' => redstone);
shaped!(r, block(Block::PISTON, 1), &["TTT", "#X#", "#R#"],
'#' => cobble, 'X' => iron, 'R' => redstone, 'T' => planks);
shaped!(r, block(Block::STICKY_PISTON, 1), &["S", "P"],
'S' => I(Item::SLIME_BALL), 'P' => B(Block::PISTON));
shaped!(r, stack(Item::BED, 1), &["###", "XXX"], '#' => B(Block::WOOL), 'X' => planks);
// `RecipeSorter`: shaped before shapeless, then by decreasing size; the sort is stable.
r.0.sort_by_key(|r| (r.shape.is_none(), Reverse(r.items.len())));
r.0
});