chore: vastly simplified recipe manager behavior

This commit is contained in:
mworzala 2024-12-01 22:03:28 -05:00 committed by Matt Worzala
parent c3a71ad10f
commit 2233ec8362
10 changed files with 222 additions and 91 deletions

View File

@ -8,6 +8,7 @@ import net.kyori.adventure.text.format.TextDecoration;
import net.minestom.demo.block.TestBlockHandler; import net.minestom.demo.block.TestBlockHandler;
import net.minestom.demo.block.placement.DripstonePlacementRule; import net.minestom.demo.block.placement.DripstonePlacementRule;
import net.minestom.demo.commands.*; import net.minestom.demo.commands.*;
import net.minestom.demo.recipe.ShapelessRecipe;
import net.minestom.server.MinecraftServer; import net.minestom.server.MinecraftServer;
import net.minestom.server.command.CommandManager; import net.minestom.server.command.CommandManager;
import net.minestom.server.event.server.ServerListPingEvent; import net.minestom.server.event.server.ServerListPingEvent;
@ -19,8 +20,7 @@ import net.minestom.server.item.ItemComponent;
import net.minestom.server.item.ItemStack; import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material; import net.minestom.server.item.Material;
import net.minestom.server.ping.ResponseData; import net.minestom.server.ping.ResponseData;
import net.minestom.server.recipe.Recipe; import net.minestom.server.recipe.RecipeBookCategory;
import net.minestom.server.recipe.RecipeCategory;
import net.minestom.server.utils.identity.NamedAndIdentified; import net.minestom.server.utils.identity.NamedAndIdentified;
import net.minestom.server.utils.time.TimeUnit; import net.minestom.server.utils.time.TimeUnit;
@ -120,28 +120,13 @@ public class Main {
//responseData.setPlayersHidden(true); //responseData.setPlayersHidden(true);
}); });
var ironBlockRecipe = new Recipe( MinecraftServer.getRecipeManager().addRecipe(new ShapelessRecipe(
"minestom:test", RecipeBookCategory.CRAFTING_MISC,
new Recipe.Shaped("", RecipeCategory.Crafting.MISC, 2, 2, List.of(Material.DIRT),
List.of(
new Recipe.Ingredient(Material.IRON_INGOT),
new Recipe.Ingredient(Material.IRON_INGOT),
new Recipe.Ingredient(Material.IRON_INGOT),
new Recipe.Ingredient(Material.IRON_INGOT)
), ItemStack.of(Material.IRON_BLOCK), true));
MinecraftServer.getRecipeManager().addRecipe(ironBlockRecipe);
var recipe = new Recipe(
"minestom:test2",
new Recipe.Shapeless("abc",
RecipeCategory.Crafting.MISC,
List.of(
new Recipe.Ingredient(Material.DIRT)
),
ItemStack.builder(Material.GOLD_BLOCK) ItemStack.builder(Material.GOLD_BLOCK)
.set(ItemComponent.CUSTOM_NAME, Component.text("abc")) .set(ItemComponent.CUSTOM_NAME, Component.text("abc"))
.build()) .build()
); ));
MinecraftServer.getRecipeManager().addRecipe(recipe);
new PlayerInit().init(); new PlayerInit().init();

View File

@ -0,0 +1,34 @@
package net.minestom.demo.recipe;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import net.minestom.server.recipe.Ingredient;
import net.minestom.server.recipe.Recipe;
import net.minestom.server.recipe.RecipeBookCategory;
import net.minestom.server.recipe.display.RecipeDisplay;
import net.minestom.server.recipe.display.SlotDisplay;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public record ShapelessRecipe(
@NotNull RecipeBookCategory recipeBookCategory,
@NotNull List<Material> ingredients,
@NotNull ItemStack result
) implements Recipe {
@Override
public @NotNull List<RecipeDisplay> createRecipeDisplays() {
return List.of(new RecipeDisplay.CraftingShapeless(
ingredients.stream().map(item -> (SlotDisplay) new SlotDisplay.Item(item)).toList(),
new SlotDisplay.ItemStack(result),
new SlotDisplay.Item(Material.CRAFTING_TABLE)
));
}
@Override
public @NotNull List<Ingredient> craftingRequirements() {
return List.of(new Ingredient(ingredients));
}
}

View File

@ -72,7 +72,6 @@ import net.minestom.server.network.packet.server.play.data.WorldPos;
import net.minestom.server.network.player.ClientSettings; import net.minestom.server.network.player.ClientSettings;
import net.minestom.server.network.player.GameProfile; import net.minestom.server.network.player.GameProfile;
import net.minestom.server.network.player.PlayerConnection; import net.minestom.server.network.player.PlayerConnection;
import net.minestom.server.recipe.Recipe;
import net.minestom.server.recipe.RecipeManager; import net.minestom.server.recipe.RecipeManager;
import net.minestom.server.registry.DynamicRegistry; import net.minestom.server.registry.DynamicRegistry;
import net.minestom.server.scoreboard.BelowNameTag; import net.minestom.server.scoreboard.BelowNameTag;
@ -340,27 +339,8 @@ public class Player extends LivingEntity implements CommandSender, HoverEventSou
// Commands // Commands
refreshCommands(); refreshCommands();
// Recipes start // Recipes
{ refreshRecipes();
RecipeManager recipeManager = MinecraftServer.getRecipeManager();
sendPacket(recipeManager.getDeclareRecipesPacket());
List<String> recipesIdentifier = new ArrayList<>();
for (Recipe recipe : recipeManager.consumeRecipes(this)) {
recipesIdentifier.add(recipe.id());
}
if (!recipesIdentifier.isEmpty()) {
// TODO(1.21.2): Recipes
// UnlockRecipesPacket unlockRecipesPacket = new UnlockRecipesPacket(0,
// false, false,
// false, false,
// false, false,
// false, false,
// recipesIdentifier, recipesIdentifier);
// sendPacket(unlockRecipesPacket);
}
}
// Recipes end
// Some client updates // Some client updates
sendPacket(getPropertiesPacket()); // Send default properties sendPacket(getPropertiesPacket()); // Send default properties
@ -561,6 +541,17 @@ public class Player extends LivingEntity implements CommandSender, HoverEventSou
sendPacket(MinecraftServer.getCommandManager().createDeclareCommandsPacket(this)); sendPacket(MinecraftServer.getCommandManager().createDeclareCommandsPacket(this));
} }
/**
* Refreshes the recipes and recipe book for this player, testing recipe predicates again.
*/
public void refreshRecipes() {
RecipeManager recipeManager = MinecraftServer.getRecipeManager();
sendPackets(
recipeManager.getDeclareRecipesPacket(),
recipeManager.createRecipeBookResetPacket(this)
);
}
@Override @Override
public boolean isOnGround() { public boolean isOnGround() {
return onGround; return onGround;

View File

@ -1,12 +1,19 @@
package net.minestom.server.listener; package net.minestom.server.listener;
import net.minestom.server.MinecraftServer;
import net.minestom.server.entity.Player; import net.minestom.server.entity.Player;
import net.minestom.server.network.packet.client.play.ClientPlaceRecipePacket; import net.minestom.server.network.packet.client.play.ClientPlaceRecipePacket;
import net.minestom.server.network.packet.server.play.PlaceGhostRecipePacket;
import net.minestom.server.recipe.RecipeManager;
import net.minestom.server.recipe.display.RecipeDisplay;
public class RecipeListener { public class RecipeListener {
public static void listener(ClientPlaceRecipePacket packet, Player player) { public static void listener(ClientPlaceRecipePacket packet, Player player) {
// TODO(1.21.2) final RecipeManager recipeManager = MinecraftServer.getRecipeManager();
// player.sendPacket(new PlaceGhostRecipePacket(packet.windowId(), packet.recipe())); final RecipeDisplay recipeDisplay = recipeManager.getRecipeDisplay(packet.recipeDisplayId(), player);
if (recipeDisplay == null) return;
player.sendPacket(new PlaceGhostRecipePacket(packet.windowId(), recipeDisplay));
} }
} }

View File

@ -4,9 +4,8 @@ import net.kyori.adventure.text.Component;
import net.minestom.server.network.NetworkBuffer; import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.network.NetworkBufferTemplate; import net.minestom.server.network.NetworkBufferTemplate;
import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacket;
import net.minestom.server.recipe.Recipe; import net.minestom.server.recipe.Ingredient;
import net.minestom.server.recipe.RecipeBookCategory; import net.minestom.server.recipe.RecipeBookCategory;
import net.minestom.server.recipe.RecipeSerializers;
import net.minestom.server.recipe.display.RecipeDisplay; import net.minestom.server.recipe.display.RecipeDisplay;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -30,7 +29,7 @@ public record RecipeBookAddPacket(@NotNull List<Entry> entries, boolean replace)
public record Entry( public record Entry(
int displayId, @NotNull RecipeDisplay display, int displayId, @NotNull RecipeDisplay display,
@Nullable Integer group, @NotNull RecipeBookCategory category, @Nullable Integer group, @NotNull RecipeBookCategory category,
@Nullable List<Recipe.Ingredient> craftingRequirements, @Nullable List<Ingredient> craftingRequirements,
byte flags byte flags
) { ) {
public static final NetworkBuffer.Type<Entry> SERIALIZER = NetworkBufferTemplate.template( public static final NetworkBuffer.Type<Entry> SERIALIZER = NetworkBufferTemplate.template(
@ -38,13 +37,13 @@ public record RecipeBookAddPacket(@NotNull List<Entry> entries, boolean replace)
RecipeDisplay.NETWORK_TYPE, Entry::display, RecipeDisplay.NETWORK_TYPE, Entry::display,
NetworkBuffer.OPTIONAL_VAR_INT, Entry::group, NetworkBuffer.OPTIONAL_VAR_INT, Entry::group,
RecipeBookCategory.NETWORK_TYPE, Entry::category, RecipeBookCategory.NETWORK_TYPE, Entry::category,
RecipeSerializers.INGREDIENT.list().optional(), Entry::craftingRequirements, Ingredient.NETWORK_TYPE.list().optional(), Entry::craftingRequirements,
NetworkBuffer.BYTE, Entry::flags, NetworkBuffer.BYTE, Entry::flags,
Entry::new); Entry::new);
public Entry(int displayId, @NotNull RecipeDisplay display, public Entry(int displayId, @NotNull RecipeDisplay display,
@Nullable Integer group, @NotNull RecipeBookCategory category, @Nullable Integer group, @NotNull RecipeBookCategory category,
@Nullable List<Recipe.Ingredient> craftingRequirements, @Nullable List<Ingredient> craftingRequirements,
boolean notification, boolean highlight) { boolean notification, boolean highlight) {
this(displayId, display, group, category, craftingRequirements, this(displayId, display, group, category, craftingRequirements,
(byte) ((notification ? FLAG_NOTIFICATION : 0) | (highlight ? FLAG_HIGHLIGHT : 0))); (byte) ((notification ? FLAG_NOTIFICATION : 0) | (highlight ? FLAG_HIGHLIGHT : 0)));

View File

@ -4,8 +4,10 @@ package net.minestom.server.recipe;
import net.minestom.server.item.Material; import net.minestom.server.item.Material;
import net.minestom.server.network.NetworkBuffer; import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.network.NetworkBufferTemplate; import net.minestom.server.network.NetworkBufferTemplate;
import net.minestom.server.recipe.display.SlotDisplay;
import net.minestom.server.utils.validate.Check; import net.minestom.server.utils.validate.Check;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -52,4 +54,15 @@ public record Ingredient(@NotNull List<@NotNull Material> items) {
public Ingredient(@NotNull Material @NotNull ... items) { public Ingredient(@NotNull Material @NotNull ... items) {
this(List.of(items)); this(List.of(items));
} }
public static @Nullable Ingredient fromSlotDisplay(@NotNull SlotDisplay slotDisplay) {
return switch (slotDisplay) {
case SlotDisplay.Item item -> new Ingredient(item.material());
case SlotDisplay.Tag ignored -> {
// TODO: Support tags in ingredients (ObjectSet for non static registries)
yield null;
}
default -> null;
};
}
} }

View File

@ -1,18 +1,54 @@
package net.minestom.server.recipe; package net.minestom.server.recipe;
import net.minestom.server.item.Material;
import net.minestom.server.recipe.display.RecipeDisplay; import net.minestom.server.recipe.display.RecipeDisplay;
import net.minestom.server.recipe.display.SlotDisplay;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Map;
public interface Recipe { public interface Recipe {
@NotNull RecipeDisplay toDisplay(); /**
* Creates recipe displays for use in the recipe book.
*
* <p>Displays should be consistent across calls and not specific to a player, they may be cached in {@link RecipeManager}.</p>
*
* <p>Note that stonecutter recipes are always sent to the client and not present in the recipe book.
* Stonecutter ingredients must be {@link SlotDisplay.Item} or {@link SlotDisplay.Tag} to be shown
* on the client.</p>
*
* @return a list of recipe displays, or none if the recipe should not be displayed in the recipe book
*/
default @NotNull List<RecipeDisplay> createRecipeDisplays() {
return List.of();
}
/** /**
* Returns the protocol recipe type. * Returns the item properties associated with this recipe. These are sent to the client to indicate
* @return * client side special slot prediction. For example, if a recipe includes {@link Material#STONE} in
* {@link RecipeProperty#FURNACE_INPUT}, the client will predict that item being placed into a furnace
* input (note that final placement is still decided by the server).
*
* <p>Item properties should be consistent across calls and not specific to a player, they may be cached in {@link RecipeManager}.</p>
*
* @return A map of item properties associated with this recipe.
*/ */
default @Nullable RecipeType recipeType() { default @NotNull Map<RecipeProperty, List<Material>> itemProperties() {
return Map.of();
}
default @Nullable String recipeBookGroup() {
return null;
}
default @Nullable RecipeBookCategory recipeBookCategory() {
return null;
}
default @Nullable List<Ingredient> craftingRequirements() {
return null; return null;
} }

View File

@ -2,71 +2,137 @@ package net.minestom.server.recipe;
import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap; import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMaps;
import net.minestom.server.entity.Player; import net.minestom.server.entity.Player;
import net.minestom.server.item.Material; import net.minestom.server.item.Material;
import net.minestom.server.network.packet.server.CachedPacket; import net.minestom.server.network.packet.server.CachedPacket;
import net.minestom.server.network.packet.server.SendablePacket; import net.minestom.server.network.packet.server.SendablePacket;
import net.minestom.server.network.packet.server.play.DeclareRecipesPacket; import net.minestom.server.network.packet.server.play.DeclareRecipesPacket;
import net.minestom.server.network.packet.server.play.RecipeBookAddPacket;
import net.minestom.server.recipe.display.RecipeDisplay; import net.minestom.server.recipe.display.RecipeDisplay;
import net.minestom.server.recipe.display.SlotDisplay; import net.minestom.server.utils.validate.Check;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate; import java.util.function.Predicate;
public final class RecipeManager { public final class RecipeManager {
private static final AtomicInteger NEXT_DISPLAY_ID = new AtomicInteger();
private record RecipeData(
@NotNull Recipe recipe,
@NotNull List<RecipeBookAddPacket.Entry> displays,
@NotNull Predicate<Player> predicate
) {
}
private final CachedPacket declareRecipesPacket = new CachedPacket(this::createDeclareRecipesPacket); private final CachedPacket declareRecipesPacket = new CachedPacket(this::createDeclareRecipesPacket);
private final Map<Recipe, Predicate<Player>> recipes = new ConcurrentHashMap<>();
private final Int2ObjectMap<RecipeDisplay> displayIdMap = new Int2ObjectArrayMap<>(); private final Map<Recipe, RecipeData> recipes = new ConcurrentHashMap<>();
private final Int2ObjectMap<Map.Entry<RecipeBookAddPacket.Entry, Predicate<Player>>> recipeBookEntryIdMap =
public void addRecipe(@NotNull Recipe recipe, @NotNull Predicate<Player> predicate) { Int2ObjectMaps.synchronize(new Int2ObjectArrayMap<>());
var previous = recipes.put(recipe, predicate);
if (previous == null) {
declareRecipesPacket.invalidate();
}
}
public void addRecipe(@NotNull Recipe recipe) { public void addRecipe(@NotNull Recipe recipe) {
addRecipe(recipe, player -> true); addRecipe(recipe, player -> true);
} }
public void removeRecipe(@NotNull Recipe recipe) { public void addRecipe(@NotNull Recipe recipe, @NotNull Predicate<Player> predicate) {
if (this.recipes.remove(recipe) != null) { List<RecipeBookAddPacket.Entry> recipeBookEntries = new ArrayList<>();
declareRecipesPacket.invalidate(); final RecipeBookCategory recipeBookCategory = recipe.recipeBookCategory();
if (recipeBookCategory != null) {
for (var display : recipe.createRecipeDisplays()) {
int displayId = NEXT_DISPLAY_ID.getAndIncrement();
recipeBookEntries.add(new RecipeBookAddPacket.Entry( //todo groups
displayId, display, null, recipeBookCategory,
recipe.craftingRequirements(), false, false
));
} }
} }
public List<Recipe> consumeRecipes(Player player) { var existingRecipe = recipes.putIfAbsent(recipe, new RecipeData(recipe, recipeBookEntries, predicate));
return recipes.entrySet().stream() Check.argCondition(existingRecipe != null, "Recipe is already registered: " + recipe);
.filter(entry -> entry.getValue().test(player)) for (RecipeBookAddPacket.Entry entry : recipeBookEntries) {
.map(Map.Entry::getKey) recipeBookEntryIdMap.put(entry.displayId(), Map.entry(entry, predicate));
.toList(); }
}
public void removeRecipe(@NotNull Recipe recipe) {
final RecipeData removed = recipes.remove(recipe);
if (removed != null) {
for (var entry : removed.displays) {
recipeBookEntryIdMap.remove(entry.displayId());
}
}
} }
public @NotNull Set<Recipe> getRecipes() { public @NotNull Set<Recipe> getRecipes() {
return recipes.keySet(); return recipes.keySet();
} }
/**
* Get the recipe display for the specified display id, optionally testing visibility against the given player.
*
* @param displayId the display id
* @param player the player to test visibility against, or null to ignore visibility
* @return the recipe display, or null if not found or not visible
*/
public @Nullable RecipeDisplay getRecipeDisplay(int displayId, @Nullable Player player) {
var recipeBookEntry = recipeBookEntryIdMap.get(displayId);
if (recipeBookEntry == null || (player != null && !recipeBookEntry.getValue().test(player))) return null;
return recipeBookEntry.getKey().display();
}
public @NotNull SendablePacket getDeclareRecipesPacket() { public @NotNull SendablePacket getDeclareRecipesPacket() {
return declareRecipesPacket; return declareRecipesPacket;
} }
/**
* Creates a {@link RecipeBookAddPacket} which replaces the recipe book with the currently unlocked
* recipes for this player.
*
* @param player the player to create the packet for
* @return the recipe book add packet with replace set to true
*/
public @NotNull RecipeBookAddPacket createRecipeBookResetPacket(@NotNull Player player) {
final List<RecipeBookAddPacket.Entry> entries = new ArrayList<>();
for (final Map.Entry<Recipe, RecipeData> recipeEntry : recipes.entrySet()) {
if (!recipeEntry.getValue().predicate.test(player)) continue;
entries.addAll(recipeEntry.getValue().displays);
}
return new RecipeBookAddPacket(entries, true);
}
private @NotNull DeclareRecipesPacket createDeclareRecipesPacket() { private @NotNull DeclareRecipesPacket createDeclareRecipesPacket() {
// Collect the special recipe entries requested by the client. // Collect the item properties for the client
final Map<RecipeProperty, List<Material>> itemProperties = new HashMap<>(); final Map<RecipeProperty, Set<Material>> itemProperties = new HashMap<>();
for (var recipe : recipes.keySet()) {
for (var entry : recipe.itemProperties().entrySet()) {
itemProperties.computeIfAbsent(entry.getKey(), k -> new HashSet<>()).addAll(entry.getValue());
}
}
final Map<RecipeProperty, List<Material>> itemPropertiesLists = new HashMap<>();
for (var entry : itemProperties.entrySet()) { // Sets to lists
itemPropertiesLists.put(entry.getKey(), new ArrayList<>(entry.getValue()));
}
// Collect the stonecutter recipes for the client
final List<DeclareRecipesPacket.StonecutterRecipe> stonecutterRecipes = new ArrayList<>(); final List<DeclareRecipesPacket.StonecutterRecipe> stonecutterRecipes = new ArrayList<>();
for (var recipeDisplay : displayIdMap.values()) { for (var recipeBookEntry : recipeBookEntryIdMap.values()) {
if (recipeDisplay instanceof RecipeDisplay.Stonecutter stonecutterDisplay) { if (!(recipeBookEntry.getKey().display() instanceof RecipeDisplay.Stonecutter stonecutterDisplay))
continue;
final Ingredient input = Ingredient.fromSlotDisplay(stonecutterDisplay.ingredient());
if (input == null) continue;
stonecutterRecipes.add(new DeclareRecipesPacket.StonecutterRecipe( stonecutterRecipes.add(new DeclareRecipesPacket.StonecutterRecipe(input, stonecutterDisplay.result()));
// Ingredient, display
))
}
} }
return new DeclareRecipesPacket(itemProperties, stonecutterRecipes); return new DeclareRecipesPacket(itemPropertiesLists, stonecutterRecipes);
} }
} }

View File

@ -23,7 +23,7 @@ import net.minestom.server.network.packet.server.login.SetCompressionPacket;
import net.minestom.server.network.packet.server.play.*; import net.minestom.server.network.packet.server.play.*;
import net.minestom.server.network.packet.server.status.ResponsePacket; import net.minestom.server.network.packet.server.status.ResponsePacket;
import net.minestom.server.network.player.GameProfile; import net.minestom.server.network.player.GameProfile;
import net.minestom.server.recipe.Recipe; import net.minestom.server.recipe.Ingredient;
import net.minestom.server.recipe.RecipeBookCategory; import net.minestom.server.recipe.RecipeBookCategory;
import net.minestom.server.recipe.RecipeProperty; import net.minestom.server.recipe.RecipeProperty;
import net.minestom.server.recipe.display.RecipeDisplay; import net.minestom.server.recipe.display.RecipeDisplay;
@ -97,11 +97,11 @@ public class PacketWriteReadTest {
RecipeProperty.BLAST_FURNACE_INPUT, List.of(Material.IRON_HOE, Material.DANDELION), RecipeProperty.BLAST_FURNACE_INPUT, List.of(Material.IRON_HOE, Material.DANDELION),
RecipeProperty.SMOKER_INPUT, List.of(Material.STONE), RecipeProperty.SMOKER_INPUT, List.of(Material.STONE),
RecipeProperty.CAMPFIRE_INPUT, List.of(Material.STONE)), RecipeProperty.CAMPFIRE_INPUT, List.of(Material.STONE)),
List.of(new DeclareRecipesPacket.StonecutterRecipe(new Recipe.Ingredient(Material.DIAMOND), List.of(new DeclareRecipesPacket.StonecutterRecipe(new Ingredient(Material.DIAMOND),
new SlotDisplay.ItemStack(ItemStack.of(Material.GOLD_BLOCK)))) new SlotDisplay.ItemStack(ItemStack.of(Material.GOLD_BLOCK))))
)); ));
SERVER_PACKETS.add(new RecipeBookAddPacket(List.of(new RecipeBookAddPacket.Entry(1, recipeDisplay, null, SERVER_PACKETS.add(new RecipeBookAddPacket(List.of(new RecipeBookAddPacket.Entry(1, recipeDisplay, null,
RecipeBookCategory.CRAFTING_MISC, List.of(new Recipe.Ingredient(Material.STONE)), true, true)), false)); RecipeBookCategory.CRAFTING_MISC, List.of(new Ingredient(Material.STONE)), true, true)), false));
SERVER_PACKETS.add(new RecipeBookRemovePacket(List.of(1))); SERVER_PACKETS.add(new RecipeBookRemovePacket(List.of(1)));
SERVER_PACKETS.add(new DestroyEntitiesPacket(List.of(5, 5, 5))); SERVER_PACKETS.add(new DestroyEntitiesPacket(List.of(5, 5, 5)));

View File

@ -11,11 +11,11 @@ public class IngredientTest {
@Test @Test
public void cannotCreateAirIngredient() { public void cannotCreateAirIngredient() {
assertThrows(IllegalArgumentException.class, () -> new Recipe.Ingredient(Material.AIR)); assertThrows(IllegalArgumentException.class, () -> new Ingredient(Material.AIR));
} }
@Test @Test
public void cannotCreateEmptyIngredient() { public void cannotCreateEmptyIngredient() {
assertThrows(IllegalArgumentException.class, () -> new Recipe.Ingredient(List.of())); assertThrows(IllegalArgumentException.class, () -> new Ingredient(List.of()));
} }
} }