mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 02:56:17 -10:00
Merge pull request #11370 from MSedgeMC/test/brewing
brewing: every recipe's mix, slot filters and the stand tick
This commit is contained in:
4 files changed
+442
No files matched your search
@@ -0,0 +1,96 @@
|
||||
//! Brewing parity (tools/test_brewing_parity.py): reads "input reagent
|
||||
//! candidate" lines of item SNBT and prints the three brewing-slot filters,
|
||||
//! the matching recipe and its assembled result; "tick" lines build a
|
||||
//! BrewingStand, run it for their tick count, and print the same state the
|
||||
//! real BrewingStandBlockEntity.serverTick oracle prints.
|
||||
//!
|
||||
//! The first input line is `#resources RESOURCES`; usage: brewing_fixture INPUT OUTPUT
|
||||
use horizon_client::{
|
||||
brewing::{Brewing, BrewingStand, brewing_fuel},
|
||||
item_data::{canon_stack, parse_stack},
|
||||
items::{ItemStack, Items},
|
||||
};
|
||||
use horizon_client_render::providers::Providers;
|
||||
use horizon_world::loot::PackOverlay;
|
||||
use std::sync::Arc;
|
||||
use std::{fmt::Write as _, fs};
|
||||
|
||||
pub fn main() -> horizon_client::Result<()> {
|
||||
let args: Vec<String> = crate::args().collect();
|
||||
let input = fs::read_to_string(args.get(1).ok_or("missing INPUT")?)?;
|
||||
let root = input
|
||||
.lines()
|
||||
.find_map(|line| line.strip_prefix("#resources "))
|
||||
.map(std::path::PathBuf::from)
|
||||
.ok_or("no #resources line")?;
|
||||
let items = Items::catalog(&root, &PackOverlay::NONE)?;
|
||||
let brewing = Brewing::load(&root, &items, &PackOverlay::NONE);
|
||||
let providers = Providers::load(&root);
|
||||
let mut out = String::new();
|
||||
for (n, line) in input
|
||||
.lines()
|
||||
.filter(|line| !line.starts_with('#'))
|
||||
.enumerate()
|
||||
{
|
||||
let f: Vec<&str> = line.split(' ').collect();
|
||||
if f[0] == "tick" {
|
||||
let ticks: u32 = f[1].parse()?;
|
||||
let mut slots: [Option<ItemStack>; 5] = Default::default();
|
||||
for i in 0..5 {
|
||||
slots[i] = parse_stack(f[2 + i])?;
|
||||
}
|
||||
let mut stand = BrewingStand::new();
|
||||
stand.items = slots;
|
||||
stand.brew_time = f[7].parse()?;
|
||||
stand.fuel = f[8].parse()?;
|
||||
stand.total_brew_time = f[9].parse()?;
|
||||
stand.total_fuel = f[10].parse()?;
|
||||
stand.speed = f[11].parse()?;
|
||||
stand.ingredient = (f[12] != "-").then(|| Arc::<str>::from(f[12]));
|
||||
let mut brews = 0;
|
||||
for _ in 0..ticks {
|
||||
brews += stand.tick(&brewing, &items, &providers).brewed as u32;
|
||||
}
|
||||
let bits: String = stand.items[..3]
|
||||
.iter()
|
||||
.map(|s| if s.is_some() { '1' } else { '0' })
|
||||
.collect();
|
||||
writeln!(
|
||||
out,
|
||||
"{n} s0={} s1={} s2={} s3={} s4={} time={} fuel={} total={} totalfuel={} speed={:.3} bits={bits} ing={} brews={brews}",
|
||||
canon_stack(stand.items[0].as_ref()),
|
||||
canon_stack(stand.items[1].as_ref()),
|
||||
canon_stack(stand.items[2].as_ref()),
|
||||
canon_stack(stand.items[3].as_ref()),
|
||||
canon_stack(stand.items[4].as_ref()),
|
||||
stand.brew_time,
|
||||
stand.fuel,
|
||||
stand.total_brew_time,
|
||||
stand.total_fuel,
|
||||
stand.speed,
|
||||
stand.ingredient.as_deref().unwrap_or("-"),
|
||||
)?;
|
||||
continue;
|
||||
}
|
||||
let input = parse_stack(f[0])?;
|
||||
let reagent = parse_stack(f[1])?;
|
||||
let candidate = parse_stack(f[2])?;
|
||||
let potion_slot = candidate.as_ref().is_some_and(|s| brewing.is_bottle(s));
|
||||
let reagent_slot = candidate.as_ref().is_some_and(|s| brewing.is_reagent(s));
|
||||
let fuel_slot = candidate
|
||||
.as_ref()
|
||||
.is_some_and(|s| brewing_fuel(&items, s).is_some());
|
||||
let found = match (input.as_ref(), reagent.as_ref()) {
|
||||
(Some(input), Some(reagent)) => brewing.find(input, reagent),
|
||||
_ => None,
|
||||
};
|
||||
let result = found.map_or_else(|| "-".to_owned(), |r| canon_stack(Some(&r.output)));
|
||||
let id = found.map_or_else(|| "-".to_owned(), |r| r.id.clone());
|
||||
writeln!(
|
||||
out,
|
||||
"{n} {potion_slot} {reagent_slot} {fuel_slot} {result} {id}"
|
||||
)?;
|
||||
}
|
||||
fs::write(args.get(2).ok_or("missing OUTPUT")?, out)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -24,6 +24,7 @@ mod block_particles_fixture;
|
||||
mod block_tint_probe;
|
||||
mod block_transform_fixture;
|
||||
mod breaking_fixture;
|
||||
mod brewing_fixture;
|
||||
mod camera_probe;
|
||||
mod cauldron_fixture;
|
||||
mod chat_screen_fixture;
|
||||
@@ -127,6 +128,7 @@ probes! {
|
||||
block_entity_tickers_fixture,
|
||||
block_particles_fixture,
|
||||
block_transform_fixture,
|
||||
brewing_fixture,
|
||||
breaking_fixture,
|
||||
block_tint_probe,
|
||||
camera_probe,
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
package dev.horizonlab.parity;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.reflect.Field;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.*;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.world.flag.FeatureFlags;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.entity.BrewingStandBlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.item.crafting.BrewingInput;
|
||||
import net.minecraft.world.item.crafting.BrewingRecipe;
|
||||
import net.minecraft.world.item.crafting.PotionIngredient;
|
||||
import net.minecraft.world.item.crafting.RecipeHolder;
|
||||
import net.minecraft.world.item.crafting.RecipeManager;
|
||||
import net.minecraft.world.item.crafting.RecipePropertySet;
|
||||
import net.minecraft.world.item.crafting.RecipeType;
|
||||
|
||||
/**
|
||||
* Original oracle for the brewing recipes: a real `RecipeManager` over the
|
||||
* vanilla data pack, its `brewing_input`/`brewing_reagent` property sets, the
|
||||
* three brewing-slot filters, and the first matching `BrewingRecipe`'s
|
||||
* `assemble`. Fixture lines are "input-snbt reagent-snbt candidate-snbt";
|
||||
* "tick" lines build a brewing stand in a block-update world and call the real
|
||||
* `BrewingStandBlockEntity.serverTick`, printing the slots, brew time, fuel,
|
||||
* speed, bottle bits, ingredient and brew-event count.
|
||||
*/
|
||||
public final class VanillaBrewingOracle {
|
||||
@SuppressWarnings("unchecked")
|
||||
public static void main(String[] args) throws Exception {
|
||||
ParityData.init();
|
||||
VanillaBlockUpdateOracle.AIR = Blocks.AIR.defaultBlockState();
|
||||
var allocator = Class.forName("sun.misc.Unsafe");
|
||||
var unsafeField = allocator.getDeclaredField("theUnsafe");
|
||||
unsafeField.setAccessible(true);
|
||||
VanillaBlockUpdateOracle.unsafe = unsafeField.get(null);
|
||||
VanillaBlockUpdateOracle.allocate = allocator.getMethod("allocateInstance", Class.class);
|
||||
var recipes = new RecipeManager(ParityData.lookup);
|
||||
recipes.finalizeRecipeLoading(FeatureFlags.VANILLA_SET);
|
||||
VanillaBlockUpdateOracle.Server.recipes = recipes;
|
||||
List<RecipeHolder<BrewingRecipe>> brewing = recipes.getRecipes().stream()
|
||||
.filter(h -> h.value() instanceof BrewingRecipe)
|
||||
.map(h -> (RecipeHolder<BrewingRecipe>) h)
|
||||
.toList();
|
||||
var inputs = recipes.propertySet(RecipePropertySet.BREWING_INPUTS);
|
||||
var reagents = recipes.propertySet(RecipePropertySet.BREWING_REAGENTS);
|
||||
Path in = Path.of(args[0]), out = Path.of(args[1]);
|
||||
int n = 0;
|
||||
try (var w = new PrintWriter(Files.newBufferedWriter(out, StandardCharsets.UTF_8));
|
||||
var lines = Files.newBufferedReader(in, StandardCharsets.UTF_8)) {
|
||||
for (String line = lines.readLine(); line != null; line = lines.readLine()) {
|
||||
if (line.startsWith("#")) continue;
|
||||
String[] f = line.split(" ");
|
||||
if (f[0].equals("tick")) {
|
||||
w.println(tick(n, f, recipes));
|
||||
n++;
|
||||
continue;
|
||||
}
|
||||
ItemStack input = ParityData.stack(f[0]);
|
||||
ItemStack reagent = ParityData.stack(f[1]);
|
||||
ItemStack candidate = ParityData.stack(f[2]);
|
||||
boolean potionSlot = !candidate.isEmpty() && PotionIngredient.isPotionInput(candidate, recipes);
|
||||
boolean reagentSlot = !candidate.isEmpty() && reagents.test(candidate);
|
||||
boolean fuelSlot = !candidate.isEmpty() && candidate.has(DataComponents.BREWING_FUEL);
|
||||
var brewingInput = new BrewingInput(input, reagent);
|
||||
Optional<RecipeHolder<BrewingRecipe>> found = brewing.stream()
|
||||
.filter(h -> h.value().matches(brewingInput))
|
||||
.findFirst();
|
||||
String result = found.map(h -> ParityData.canon(h.value().assemble(brewingInput))).orElse("-");
|
||||
String id = found.map(h -> h.id().identifier().toString()).orElse("-");
|
||||
w.println(n + " " + potionSlot + " " + reagentSlot + " " + fuelSlot + " " + result + " " + id);
|
||||
n++;
|
||||
}
|
||||
}
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
private static Field field(Class<?> c, String name) throws Exception {
|
||||
Field f = c.getDeclaredField(name);
|
||||
f.setAccessible(true);
|
||||
return f;
|
||||
}
|
||||
|
||||
private static void setInt(Object o, Class<?> c, String name, int v) throws Exception {
|
||||
field(c, name).setInt(o, v);
|
||||
}
|
||||
|
||||
/** One brewing-stand tick run: initial slots/fields, `ticks` serverTick calls, final state. */
|
||||
private static String tick(int n, String[] f, RecipeManager recipes) throws Exception {
|
||||
int ticks = Integer.parseInt(f[1]);
|
||||
ItemStack[] slots = new ItemStack[5];
|
||||
for (int i = 0; i < 5; i++) slots[i] = ParityData.stack(f[2 + i]);
|
||||
int brewTime = Integer.parseInt(f[7]);
|
||||
int fuel = Integer.parseInt(f[8]);
|
||||
int totalBrew = Integer.parseInt(f[9]);
|
||||
int totalFuel = Integer.parseInt(f[10]);
|
||||
float speed = Float.parseFloat(f[11]);
|
||||
String ingredient = f[12];
|
||||
|
||||
JsonObject caseJson = new JsonObject();
|
||||
JsonArray cells = new JsonArray();
|
||||
JsonArray cell = new JsonArray();
|
||||
cell.add(0);
|
||||
cell.add(0);
|
||||
cell.add(0);
|
||||
cell.add(Block.getId(Blocks.BREWING_STAND.defaultBlockState()));
|
||||
cells.add(cell);
|
||||
caseJson.add("cells", cells);
|
||||
caseJson.addProperty("seed", 0);
|
||||
VanillaBlockUpdateOracle.World w = VanillaBlockUpdateOracle.build(caseJson);
|
||||
w.registries = ParityData.access;
|
||||
var pos = new BlockPos(0, 0, 0);
|
||||
BlockState state = w.getBlockState(pos);
|
||||
var be = (BrewingStandBlockEntity) w.getBlockEntity(pos);
|
||||
for (int i = 0; i < 5; i++) be.setItem(i, slots[i]);
|
||||
setInt(be, BrewingStandBlockEntity.class, "brewTime", brewTime);
|
||||
setInt(be, BrewingStandBlockEntity.class, "fuel", fuel);
|
||||
setInt(be, BrewingStandBlockEntity.class, "totalBrewTime", totalBrew);
|
||||
setInt(be, BrewingStandBlockEntity.class, "totalFuel", totalFuel);
|
||||
field(BrewingStandBlockEntity.class, "speedMultiplier").setFloat(be, speed);
|
||||
Item ingredientItem = ingredient.equals("-") ? null
|
||||
: BuiltInRegistries.ITEM.getValue(Identifier.parse(ingredient));
|
||||
field(BrewingStandBlockEntity.class, "ingredient").set(be, ingredientItem);
|
||||
for (int t = 0; t < ticks; t++) {
|
||||
BrewingStandBlockEntity.serverTick(w, pos, state, be);
|
||||
}
|
||||
int brews = 0;
|
||||
for (int at = w.log.indexOf("L 1035 "); at >= 0; at = w.log.indexOf("L 1035 ", at + 1)) brews++;
|
||||
StringBuilder sb = new StringBuilder(Integer.toString(n));
|
||||
for (int i = 0; i < 5; i++) sb.append(" s").append(i).append('=').append(ParityData.canon(be.getItem(i)));
|
||||
sb.append(" time=").append(field(BrewingStandBlockEntity.class, "brewTime").getInt(be))
|
||||
.append(" fuel=").append(field(BrewingStandBlockEntity.class, "fuel").getInt(be))
|
||||
.append(" total=").append(field(BrewingStandBlockEntity.class, "totalBrewTime").getInt(be))
|
||||
.append(" totalfuel=").append(field(BrewingStandBlockEntity.class, "totalFuel").getInt(be))
|
||||
.append(" speed=").append(String.format(Locale.ROOT, "%.3f",
|
||||
field(BrewingStandBlockEntity.class, "speedMultiplier").getFloat(be)))
|
||||
.append(" bits=");
|
||||
for (int i = 0; i < 3; i++) sb.append(be.getItem(i).isEmpty() ? '0' : '1');
|
||||
Item ing = (Item) field(BrewingStandBlockEntity.class, "ingredient").get(be);
|
||||
sb.append(" ing=").append(ing == null ? "-" : BuiltInRegistries.ITEM.getKey(ing).toString())
|
||||
.append(" brews=").append(brews);
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compare the brewing recipes' slot rules, matching and assembled output with vanilla.
|
||||
|
||||
The Java oracle loads the vanilla data pack's real `RecipeManager`, takes its
|
||||
`brewing_input` and `brewing_reagent` property sets and the `BrewingRecipe`s,
|
||||
checks all three brewing-slot filters and calls the first matching recipe's
|
||||
`assemble`. The Rust probe calls `brewing::Brewing::is_bottle`/`is_reagent`,
|
||||
`brewing_fuel` and `Brewing::find`. Cases cover every bundled brewing recipe,
|
||||
every potion type and potion id against every reagent, candidates from the
|
||||
whole item registry, and non-matching pairs. Tick cases run
|
||||
`BrewingStandBlockEntity.serverTick` and `BrewingStand::tick` for 0..8001
|
||||
ticks: the 400-tick brew time divided by `speed_multiplier` and its ceiling,
|
||||
blaze powder's 20 fuel uses, fuel loaded with nothing to brew, splash and
|
||||
lingering outputs, ingredient shrink and mismatch, the brew sound, and the
|
||||
bottle bits. `--no-tick` leaves the tick corpus out, and `--baseline` keeps
|
||||
only the exact recipe pairs.
|
||||
"""
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
try:
|
||||
from . import lab, oracle_harness as harness
|
||||
except ImportError:
|
||||
import lab
|
||||
import oracle_harness as harness
|
||||
|
||||
POTION_ITEMS = ['potion', 'splash_potion', 'lingering_potion']
|
||||
|
||||
|
||||
def stack(item, potion=None, count=1):
|
||||
if potion:
|
||||
return '{id:"%s",count:%d,components:{"minecraft:potion_contents":{potion:"%s"}}}' % (
|
||||
item, count, potion)
|
||||
return '{id:"%s",count:%d}' % (item, count)
|
||||
|
||||
|
||||
def recipes(resources):
|
||||
out = []
|
||||
for path in sorted((resources / 'data/minecraft/recipe/brewing').glob('*.json')):
|
||||
data = json.loads(path.read_text())
|
||||
if data.get('type') != 'minecraft:brewing':
|
||||
continue
|
||||
potion = data['input'].get('potion_contents', {}).get('potions')
|
||||
out.append({
|
||||
'input': stack(data['input']['item'], potion),
|
||||
'reagent': stack(data['reagent']['item']),
|
||||
'output': data['output'],
|
||||
})
|
||||
return out
|
||||
|
||||
|
||||
def potion(kind='potion', name='water', count=1):
|
||||
return '{id:"minecraft:%s",count:%d,components:{"minecraft:potion_contents":{potion:"minecraft:%s"}}}' % (
|
||||
kind, count, name)
|
||||
|
||||
|
||||
def tick_case(ticks, slots, brew_time=0, fuel=0, total=0, total_fuel=20, speed=1.0,
|
||||
ingredient='-'):
|
||||
filled = [slot if slot else '-' for slot in slots]
|
||||
return 'tick %d %s %d %d %d %d %s %s' % (
|
||||
ticks, ' '.join(filled), brew_time, fuel, total, total_fuel, speed, ingredient)
|
||||
|
||||
|
||||
def tick_cases():
|
||||
water = potion()
|
||||
awkward = potion(name='awkward')
|
||||
splash = potion('splash_potion')
|
||||
lingering = potion('lingering_potion')
|
||||
wart = stack('minecraft:nether_wart')
|
||||
wart2 = stack('minecraft:nether_wart', count=2)
|
||||
wart5 = stack('minecraft:nether_wart', count=5)
|
||||
wart21 = stack('minecraft:nether_wart', count=21)
|
||||
blaze = stack('minecraft:blaze_powder')
|
||||
blaze2 = '{id:"minecraft:blaze_powder",count:2}'
|
||||
redstone = stack('minecraft:redstone')
|
||||
out = []
|
||||
# Brew-time countdown, with fuel already in the tank.
|
||||
for ticks in (0, 1, 2, 399, 400, 401, 402, 800):
|
||||
out.append(tick_case(ticks, [water, None, None, wart, None], fuel=20,
|
||||
total=400, ingredient='minecraft:nether_wart'))
|
||||
# Blaze powder loads the tank (uses=20, speed=1), then the brew runs.
|
||||
for ticks in (0, 1, 2, 399, 400, 401):
|
||||
out.append(tick_case(ticks, [water, None, None, wart, blaze], total=0,
|
||||
ingredient='minecraft:nether_wart'))
|
||||
out.append(tick_case(1, [water, None, None, wart, blaze2], total=0,
|
||||
ingredient='minecraft:nether_wart'))
|
||||
# Fuel loads even with nothing to brew.
|
||||
out.append(tick_case(1, [None, None, None, wart, blaze], total=0,
|
||||
ingredient='minecraft:nether_wart'))
|
||||
out.append(tick_case(1, [None, None, None, None, blaze], total=0))
|
||||
# Brew time is ceil(400 / speed) and speed 0 falls back to 1.
|
||||
for speed, finish in ((2.0, 200), (3.0, 134), (0.3, 1334), (0.0, 400)):
|
||||
for ticks in (1, finish, finish + 1, finish + 2):
|
||||
out.append(tick_case(ticks, [water, None, None, wart, None], fuel=5,
|
||||
total=0, speed=speed, ingredient='minecraft:nether_wart'))
|
||||
# Only the slots with a recipe change; the no-recipe bottle stays.
|
||||
for ticks in (1, 400, 401):
|
||||
out.append(tick_case(ticks, [water, awkward, water, wart, None], fuel=20,
|
||||
total=400, ingredient='minecraft:nether_wart'))
|
||||
# The ingredient stack shrinks; the last one leaves an empty slot.
|
||||
for wart_stack, ticks in ((wart2, 400), (wart5, 400), (stack('minecraft:nether_wart'), 400)):
|
||||
out.append(tick_case(ticks, [water, None, None, wart_stack, None], fuel=20,
|
||||
total=400, ingredient='minecraft:nether_wart'))
|
||||
# Changing the ingredient mid-brew clears the progress; finishing does not need fuel.
|
||||
out.append(tick_case(1, [water, None, None, redstone, None], brew_time=200, fuel=5,
|
||||
total=400, ingredient='minecraft:nether_wart'))
|
||||
out.append(tick_case(1, [water, None, None, wart, None], brew_time=1, fuel=0,
|
||||
total=400, ingredient='minecraft:nether_wart'))
|
||||
out.append(tick_case(1, [water, None, None, None, None], brew_time=1, fuel=5,
|
||||
total=400, ingredient='minecraft:nether_wart'))
|
||||
# Splash and lingering potions keep their kind through the mix.
|
||||
out.append(tick_case(401, [splash, None, None, wart, None], fuel=20, total=400,
|
||||
ingredient='minecraft:nether_wart'))
|
||||
out.append(tick_case(401, [lingering, None, None, wart, None], fuel=20, total=400,
|
||||
ingredient='minecraft:nether_wart'))
|
||||
# Twenty brews from one blaze powder's twenty uses; the 21st ingredient stays.
|
||||
out.append(tick_case(8001, [water, None, None, wart21, blaze], total=0,
|
||||
ingredient='minecraft:nether_wart'))
|
||||
out.append(tick_case(8002, [water, None, None, wart21, blaze], total=0,
|
||||
ingredient='minecraft:nether_wart'))
|
||||
# Bottle bits are set from the slots on the first tick.
|
||||
for slots in ([None, None, None], [water, None, None], [water, None, water],
|
||||
[water, awkward, water]):
|
||||
out.append(tick_case(1, slots + [None, None], fuel=20))
|
||||
return out
|
||||
|
||||
|
||||
def make_cases(build):
|
||||
resources = lab.LOCAL / 'horizon-client' / '26.3'
|
||||
catalog = json.loads((resources / 'items.json').read_text())
|
||||
all_recipes = recipes(resources)
|
||||
potions = sorted({p for r in all_recipes
|
||||
for p in (r['output'].get('components', {})
|
||||
.get('minecraft:potion_contents', {}).get('potion', ''),)
|
||||
if p})
|
||||
potion_ids = sorted({r['input'].split('"potion":"')[1].split('"')[0]
|
||||
for r in all_recipes if '"potion":"' in r['input']})
|
||||
reagents = sorted({r['reagent'] for r in all_recipes})
|
||||
cases = []
|
||||
# The baseline corpus: each bundled recipe's exact pair with each of the
|
||||
# three slots probed. --baseline reproduces it without the cross-product.
|
||||
for r in all_recipes:
|
||||
for candidate in (r['input'], r['reagent'], stack('minecraft:blaze_powder'),
|
||||
stack('minecraft:stick')):
|
||||
cases.append(f"{r['input']} {r['reagent']} {candidate}")
|
||||
if '--baseline' not in sys.argv:
|
||||
# Every potion item and potion id against every reagent, plus a few
|
||||
# mismatches and empty sides.
|
||||
for item in POTION_ITEMS:
|
||||
for potion in potion_ids + ['minecraft:water', 'minecraft:awkward']:
|
||||
for reagent in reagents:
|
||||
inp = stack('minecraft:' + item, potion)
|
||||
cases.append(f"{inp} {reagent} {inp}")
|
||||
cases.append(f"{stack('minecraft:' + item, potion)} {stack('minecraft:stick')} -")
|
||||
cases.append(f"- {stack('minecraft:stick')} {stack('minecraft:' + item, potion)}")
|
||||
# Every item as a filter candidate, so the slot rules are compared on
|
||||
# the whole registry and not only on the recipe inputs.
|
||||
for item in sorted(catalog):
|
||||
if item != 'minecraft:air':
|
||||
cases.append(f"- - {stack(item)}")
|
||||
for potion in potion_ids:
|
||||
for item in POTION_ITEMS:
|
||||
cases.append(f"- - {stack('minecraft:' + item, potion)}")
|
||||
if '--no-tick' not in sys.argv:
|
||||
cases += tick_cases()
|
||||
# Duplicate-free while keeping the first occurrence order.
|
||||
seen = set()
|
||||
unique = []
|
||||
for case in cases:
|
||||
if case not in seen:
|
||||
seen.add(case)
|
||||
unique.append(case)
|
||||
path = build / 'cases.txt'
|
||||
path.write_text(f'#resources {resources}\n' + ''.join(c + '\n' for c in unique))
|
||||
return path, len(unique)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
harness.differential(
|
||||
'brewing-parity', 'VanillaBrewingOracle', 'brewing_fixture', make_cases,
|
||||
'BrewingRecipe matching and assemble over the vanilla brewing data, the brewing_input and '
|
||||
'brewing_reagent property sets and the potion, reagent and fuel slot filters, for every '
|
||||
'bundled recipe, potion type/id and item candidate, plus the brewing-stand tick: brew time and '
|
||||
'speed multiplier, fuel uses and loading, ingredient shrink, outputs and bottle bits. --baseline '
|
||||
'leaves out the cross-product and whole-registry slot candidates; --no-tick leaves out the tick cases.',
|
||||
sources=[lab.ROOT / 'crates/horizon-client-render/src/brewing.rs',
|
||||
lab.ROOT / 'crates/horizon-items/src/item_data.rs',
|
||||
lab.ROOT / 'crates/horizon-items/src/items.rs'])
|
||||
Reference in new issue
Block a user