mirror of
https://github.com/Minestom/Minestom.git
synced 2025-02-11 18:02:08 +01:00
chore: vastly simplified recipe manager behavior
This commit is contained in:
parent
c3a71ad10f
commit
2233ec8362
@ -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();
|
||||||
|
|
||||||
|
@ -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));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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;
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)));
|
||||||
|
@ -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;
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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)));
|
||||||
|
@ -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()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user