Re-implement on-the-fly recipe sending

This commit is contained in:
Camotoy 2024-10-28 18:53:16 -04:00
parent 344d40dd1f
commit 05f153c941
No known key found for this signature in database
GPG key ID: 7EEFB66FE798081F
11 changed files with 170 additions and 98 deletions

View file

@ -29,6 +29,7 @@ import lombok.*;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.registry.Registries;
@ -38,6 +39,10 @@ import org.geysermc.geyser.translator.item.ItemTranslator;
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.EmptySlotDisplay;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.ItemSlotDisplay;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.ItemStackSlotDisplay;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.SlotDisplay;
import java.util.HashMap;
@ -77,6 +82,20 @@ public class GeyserItemStack {
return itemStack == null ? EMPTY : new GeyserItemStack(itemStack.getId(), itemStack.getAmount(), itemStack.getDataComponents());
}
public static @NonNull GeyserItemStack from(@NonNull SlotDisplay slotDisplay) {
if (slotDisplay instanceof EmptySlotDisplay) {
return GeyserItemStack.EMPTY;
}
if (slotDisplay instanceof ItemSlotDisplay itemSlotDisplay) {
return GeyserItemStack.of(itemSlotDisplay.item(), 1);
}
if (slotDisplay instanceof ItemStackSlotDisplay itemStackSlotDisplay) {
return GeyserItemStack.from(itemStackSlotDisplay.itemStack());
}
GeyserImpl.getInstance().getLogger().warning("Unsure how to convert to ItemStack: " + slotDisplay);
return GeyserItemStack.EMPTY;
}
public int getJavaId() {
return isEmpty() ? 0 : javaId;
}

View file

@ -26,7 +26,7 @@
package org.geysermc.geyser.inventory.recipe;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.SlotDisplay;
/**
* A more compact version of {link org.geysermc.mcprotocollib.protocol.data.game.recipe.Recipe}.
@ -38,5 +38,5 @@ public interface GeyserRecipe {
boolean isShaped();
@Nullable
ItemStack result();
SlotDisplay result();
}

View file

@ -26,14 +26,16 @@
package org.geysermc.geyser.inventory.recipe;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.Ingredient;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.ShapedCraftingRecipeDisplay;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.SlotDisplay;
public record GeyserShapedRecipe(int width, int height, Ingredient[] ingredients, @Nullable ItemStack result) implements GeyserRecipe {
import java.util.List;
// public GeyserShapedRecipe(ShapedRecipeData data) {
// this(data.getWidth(), data.getHeight(), data.getIngredients(), data.getResult());
// }
public record GeyserShapedRecipe(int width, int height, List<SlotDisplay> ingredients, @Nullable SlotDisplay result) implements GeyserRecipe {
public GeyserShapedRecipe(ShapedCraftingRecipeDisplay data) {
this(data.width(), data.height(), data.ingredients(), data.result());
}
@Override
public boolean isShaped() {

View file

@ -26,14 +26,16 @@
package org.geysermc.geyser.inventory.recipe;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.Ingredient;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.ShapelessCraftingRecipeDisplay;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.SlotDisplay;
public record GeyserShapelessRecipe(Ingredient[] ingredients, @Nullable ItemStack result) implements GeyserRecipe {
import java.util.List;
// public GeyserShapelessRecipe(ShapelessRecipeData data) {
// this(data.getIngredients(), data.getResult());
// }
public record GeyserShapelessRecipe(List<SlotDisplay> ingredients, @Nullable SlotDisplay result) implements GeyserRecipe {
public GeyserShapelessRecipe(ShapelessCraftingRecipeDisplay data) {
this(data.ingredients(), data.result());
}
@Override
public boolean isShaped() {

View file

@ -31,8 +31,6 @@ import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.nbt.NbtType;
import org.geysermc.geyser.inventory.recipe.GeyserRecipe;
import org.geysermc.geyser.inventory.recipe.GeyserShapedRecipe;
import org.geysermc.geyser.inventory.recipe.GeyserShapelessRecipe;
import org.geysermc.mcprotocollib.protocol.codec.MinecraftCodecHelper;
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;
@ -92,7 +90,7 @@ public abstract class RecipeRegistryLoader implements RegistryLoader<String, Map
for (int i = 0; i < rawInputs.size(); i++) {
//javaInputs[i] = new Ingredient(new ItemStack[] {toItemStack(rawInputs.get(i), helper)});
}
deserializedRecipes.add(new GeyserShapelessRecipe(javaInputs, output));
//deserializedRecipes.add(new GeyserShapelessRecipe(javaInputs, output));
}
return deserializedRecipes;
}
@ -118,7 +116,7 @@ public abstract class RecipeRegistryLoader implements RegistryLoader<String, Map
//inputs[i++] = new Ingredient(new ItemStack[] {stack});
}
}
deserializedRecipes.add(new GeyserShapedRecipe(shape.size(), shape.get(0).length, inputs, output));
//deserializedRecipes.add(new GeyserShapedRecipe(shape.size(), shape.get(0).length, inputs, output));
}
return deserializedRecipes;
}

View file

@ -446,8 +446,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
*/
private final Int2ObjectMap<List<String>> javaToBedrockRecipeIds;
@Setter
private Int2ObjectMap<GeyserRecipe> craftingRecipes;
private final Int2ObjectMap<GeyserRecipe> craftingRecipes;
private final AtomicInteger lastRecipeNetId;
/**

View file

@ -25,21 +25,40 @@
package org.geysermc.geyser.translator.inventory;
import it.unimi.dsi.fastutil.ints.*;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntIterator;
import it.unimi.dsi.fastutil.ints.IntLinkedOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import it.unimi.dsi.fastutil.ints.IntSortedSet;
import lombok.AllArgsConstructor;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerSlotType;
import org.cloudburstmc.protocol.bedrock.data.inventory.FullContainerName;
import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.request.ItemStackRequest;
import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.request.ItemStackRequestSlotData;
import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.request.action.*;
import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.request.action.AutoCraftRecipeAction;
import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.request.action.ConsumeAction;
import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.request.action.CraftResultsDeprecatedAction;
import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.request.action.DropAction;
import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.request.action.ItemStackRequestAction;
import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.request.action.SwapAction;
import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.request.action.TransferItemStackRequestAction;
import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.response.ItemStackResponse;
import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.response.ItemStackResponseContainer;
import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.response.ItemStackResponseSlot;
import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.response.ItemStackResponseStatus;
import org.cloudburstmc.protocol.bedrock.packet.ItemStackResponsePacket;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.inventory.*;
import org.geysermc.geyser.inventory.BedrockContainerSlot;
import org.geysermc.geyser.inventory.CartographyContainer;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.inventory.PlayerInventory;
import org.geysermc.geyser.inventory.SlotType;
import org.geysermc.geyser.inventory.click.Click;
import org.geysermc.geyser.inventory.click.ClickPlan;
import org.geysermc.geyser.inventory.recipe.GeyserRecipe;
@ -56,12 +75,17 @@ import org.geysermc.geyser.translator.inventory.furnace.SmokerInventoryTranslato
import org.geysermc.geyser.util.InventoryUtils;
import org.geysermc.geyser.util.ItemUtils;
import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.Ingredient;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.EmptySlotDisplay;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.SlotDisplay;
import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@AllArgsConstructor
public abstract class InventoryTranslator {
@ -642,8 +666,8 @@ public abstract class InventoryTranslator {
}
int gridDimensions = gridSize == 4 ? 2 : 3;
Ingredient[] ingredients = new Ingredient[0];
ItemStack output = null;
List<SlotDisplay> ingredients = Collections.emptyList();
SlotDisplay output = null;
int recipeWidth = 0;
int ingRemaining = 0;
int ingredientIndex = -1;
@ -697,7 +721,7 @@ public abstract class InventoryTranslator {
ingredients = shapelessRecipe.ingredients();
recipeWidth = gridDimensions;
output = shapelessRecipe.result();
if (ingredients.length > gridSize) {
if (ingredients.size() > gridSize) {
return rejectRequest(request);
}
}
@ -728,11 +752,11 @@ public abstract class InventoryTranslator {
craftState = CraftState.INGREDIENTS;
if (ingRemaining == 0) {
while (++ingredientIndex < ingredients.length) {
// if (ingredients[ingredientIndex].getOptions().length != 0) {
// ingRemaining = timesCrafted;
// break;
// }
while (++ingredientIndex < ingredients.size()) {
if (!(ingredients.get(ingredientIndex) instanceof EmptySlotDisplay)) { // TODO I guess can technically other options be empty?
ingRemaining = timesCrafted;
break;
}
}
}

View file

@ -42,7 +42,7 @@ import java.util.UUID;
import static org.geysermc.geyser.util.InventoryUtils.LAST_RECIPE_NET_ID;
@Translator(packet = ClientboundFinishConfigurationPacket.class)
public class JavaFinishConfigurationPacketTranslator extends PacketTranslator<ClientboundFinishConfigurationPacket> {
public class JavaFinishConfigurationTranslator extends PacketTranslator<ClientboundFinishConfigurationPacket> {
/**
* Required to use the specified cartography table recipes
*/
@ -72,6 +72,9 @@ public class JavaFinishConfigurationPacketTranslator extends PacketTranslator<Cl
craftingDataPacket.getPotionMixData().addAll(Registries.POTION_MIXES.forVersion(session.getUpstream().getProtocolVersion()));
if (session.isSentSpawnPacket()) {
session.getUpstream().sendPacket(craftingDataPacket);
session.getCraftingRecipes().clear();
session.getJavaToBedrockRecipeIds().clear();
session.getStonecutterRecipes().clear();
} else {
session.getUpstream().queuePostStartGamePacket(craftingDataPacket);
}

View file

@ -39,6 +39,9 @@ import org.cloudburstmc.protocol.bedrock.data.inventory.descriptor.DefaultDescri
import org.cloudburstmc.protocol.bedrock.data.inventory.descriptor.ItemDescriptorWithCount;
import org.cloudburstmc.protocol.bedrock.packet.CraftingDataPacket;
import org.cloudburstmc.protocol.bedrock.packet.UnlockedRecipesPacket;
import org.geysermc.geyser.inventory.recipe.GeyserRecipe;
import org.geysermc.geyser.inventory.recipe.GeyserShapedRecipe;
import org.geysermc.geyser.inventory.recipe.GeyserShapelessRecipe;
import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.item.type.BedrockRequiresTagItem;
import org.geysermc.geyser.item.type.Item;
@ -79,9 +82,10 @@ public class JavaRecipeBookAddTranslator extends PacketTranslator<ClientboundRec
@Override
public void translate(GeyserSession session, ClientboundRecipeBookAddPacket packet) {
//System.out.println(packet);
System.out.println(packet);
int netId = session.getLastRecipeNetId().get();
Int2ObjectMap<List<String>> javaToBedrockRecipeIds = session.getJavaToBedrockRecipeIds();
Int2ObjectMap<GeyserRecipe> geyserRecipes = session.getCraftingRecipes();
CraftingDataPacket craftingDataPacket = new CraftingDataPacket();
UnlockedRecipesPacket recipesPacket = new UnlockedRecipesPacket();
@ -101,14 +105,17 @@ public class JavaRecipeBookAddTranslator extends PacketTranslator<ClientboundRec
List<String> bedrockRecipeIds = new ArrayList<>();
ItemData output = bedrockRecipes.right();
List<List<ItemDescriptorWithCount>> left = bedrockRecipes.left();
GeyserRecipe geyserRecipe = new GeyserShapedRecipe(shapedRecipe);
for (int i = 0; i < left.size(); i++) {
List<ItemDescriptorWithCount> inputs = left.get(i);
String recipeId = contents.id() + "_" + i;
int recipeNetworkId = netId++;
craftingDataPacket.getCraftingData().add(ShapedRecipeData.shaped(recipeId,
shapedRecipe.width(), shapedRecipe.height(), inputs,
Collections.singletonList(output), UUID.randomUUID(), "crafting_table", 0, netId++, false, RecipeUnlockingRequirement.INVALID));
Collections.singletonList(output), UUID.randomUUID(), "crafting_table", 0, recipeNetworkId, false, RecipeUnlockingRequirement.INVALID));
recipesPacket.getUnlockedRecipes().add(recipeId);
bedrockRecipeIds.add(recipeId);
geyserRecipes.put(recipeNetworkId, geyserRecipe);
}
javaToBedrockRecipeIds.put(contents.id(), List.copyOf(bedrockRecipeIds));
}
@ -121,13 +128,16 @@ public class JavaRecipeBookAddTranslator extends PacketTranslator<ClientboundRec
List<String> bedrockRecipeIds = new ArrayList<>();
ItemData output = bedrockRecipes.right();
List<List<ItemDescriptorWithCount>> left = bedrockRecipes.left();
GeyserRecipe geyserRecipe = new GeyserShapelessRecipe(shapelessRecipe);
for (int i = 0; i < left.size(); i++) {
List<ItemDescriptorWithCount> inputs = left.get(i);
String recipeId = contents.id() + "_" + i;
int recipeNetworkId = netId++;
craftingDataPacket.getCraftingData().add(ShapelessRecipeData.shapeless(recipeId,
inputs, Collections.singletonList(output), UUID.randomUUID(), "crafting_table", 0, netId++, RecipeUnlockingRequirement.INVALID));
inputs, Collections.singletonList(output), UUID.randomUUID(), "crafting_table", 0, recipeNetworkId, RecipeUnlockingRequirement.INVALID));
recipesPacket.getUnlockedRecipes().add(recipeId);
bedrockRecipeIds.add(recipeId);
geyserRecipes.put(recipeNetworkId, geyserRecipe);
}
javaToBedrockRecipeIds.put(contents.id(), List.copyOf(bedrockRecipeIds));
}

View file

@ -44,12 +44,15 @@ import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
import org.geysermc.geyser.util.InventoryUtils;
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.HolderSet;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.Ingredient;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.EmptySlotDisplay;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.ItemStackSlotDisplay;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.SlotDisplay;
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.inventory.ClientboundContainerSetSlotPacket;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@ -104,9 +107,6 @@ public class JavaContainerSetSlotTranslator extends PacketTranslator<Clientbound
* Checks for a changed output slot in the crafting grid, and ensures Bedrock sees the recipe.
*/
private static void updateCraftingGrid(GeyserSession session, int slot, ItemStack item, Inventory inventory, InventoryTranslator translator) {
if (true) {
return;
}
// Check if it's the crafting grid result slot.
if (slot != 0) {
return;
@ -166,14 +166,13 @@ public class JavaContainerSetSlotTranslator extends PacketTranslator<Clientbound
ItemData[] ingredients = new ItemData[height * width];
//construct ingredient list and clear slots on client
Ingredient[] javaIngredients = new Ingredient[height * width];
List<SlotDisplay> javaIngredients = new ArrayList<>(height * width);
int index = 0;
for (int row = firstRow; row < height + firstRow; row++) {
for (int col = firstCol; col < width + firstCol; col++) {
GeyserItemStack geyserItemStack = inventory.getItem(col + (row * gridDimensions) + 1);
ingredients[index] = geyserItemStack.getItemData(session);
int[] items = new int[] {geyserItemStack.isEmpty() ? 0 : geyserItemStack.getJavaId()};
javaIngredients[index] = new Ingredient(new HolderSet(items));
javaIngredients.add(geyserItemStack.isEmpty() ? new EmptySlotDisplay() : new ItemStackSlotDisplay(geyserItemStack.getItemStack()));
InventorySlotPacket slotPacket = new InventorySlotPacket();
slotPacket.setContainerId(ContainerId.UI);
@ -185,7 +184,7 @@ public class JavaContainerSetSlotTranslator extends PacketTranslator<Clientbound
}
// Cache this recipe so we know the client has received it
session.getCraftingRecipes().put(newRecipeId, new GeyserShapedRecipe(width, height, javaIngredients, item));
session.getCraftingRecipes().put(newRecipeId, new GeyserShapedRecipe(width, height, javaIngredients, new ItemStackSlotDisplay(item)));
CraftingDataPacket craftPacket = new CraftingDataPacket();
craftPacket.getCraftingData().add(ShapedRecipeData.shaped(

View file

@ -51,6 +51,8 @@ import org.geysermc.geyser.level.BedrockDimension;
import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.registry.type.ItemMappings;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.registry.JavaRegistries;
import org.geysermc.geyser.session.cache.tags.Tag;
import org.geysermc.geyser.text.ChatColor;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
@ -59,15 +61,21 @@ import org.geysermc.geyser.translator.inventory.chest.DoubleChestInventoryTransl
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode;
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.Ingredient;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.CompositeSlotDisplay;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.EmptySlotDisplay;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.ItemSlotDisplay;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.ItemStackSlotDisplay;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.SlotDisplay;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.TagSlotDisplay;
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.inventory.ServerboundContainerClosePacket;
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.inventory.ServerboundPickItemPacket;
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.inventory.ServerboundSetCreativeModeSlotPacket;
import org.jetbrains.annotations.Contract;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.function.IntFunction;
@ -441,6 +449,35 @@ public class InventoryUtils {
};
}
/**
* Returns if the provided item stack would be accepted by the slot display.
*/
public static boolean acceptsAsInput(GeyserSession session, SlotDisplay slotDisplay, GeyserItemStack itemStack) {
if (slotDisplay instanceof EmptySlotDisplay) {
return itemStack.isEmpty();
}
if (slotDisplay instanceof CompositeSlotDisplay compositeSlotDisplay) {
if (compositeSlotDisplay.contents().size() == 1) {
return acceptsAsInput(session, compositeSlotDisplay.contents().get(0), itemStack);
}
return compositeSlotDisplay.contents().stream().anyMatch(aSlotDisplay -> acceptsAsInput(session, aSlotDisplay, itemStack));
}
if (slotDisplay instanceof ItemSlotDisplay itemSlotDisplay) {
return itemStack.getJavaId() == itemSlotDisplay.item();
}
if (slotDisplay instanceof ItemStackSlotDisplay itemStackSlotDisplay) {
ItemStack other = itemStackSlotDisplay.itemStack();
// Amount check might be flimsy?
return itemStack.getJavaId() == other.getId() && itemStack.getAmount() >= other.getAmount()
&& Objects.equals(itemStack.getComponents(), other.getDataComponents());
}
if (slotDisplay instanceof TagSlotDisplay tagSlotDisplay) {
return session.getTagCache().is(new Tag<>(JavaRegistries.ITEM, tagSlotDisplay.tag()), itemStack.asItem());
}
session.getGeyser().getLogger().warning("Unknown slot display type: " + slotDisplay);
return false;
}
/**
* Test all known recipes to find a valid match
*
@ -462,91 +499,70 @@ public class InventoryUtils {
for (GeyserRecipe recipe : session.getCraftingRecipes().values()) {
if (recipe.isShaped()) {
GeyserShapedRecipe shapedRecipe = (GeyserShapedRecipe) recipe;
if (output != null && !shapedRecipe.result().equals(output)) {
if (output != null && !acceptsAsInput(session, shapedRecipe.result(), GeyserItemStack.from(output))) {
continue;
}
Ingredient[] ingredients = shapedRecipe.ingredients();
if (shapedRecipe.width() != width || shapedRecipe.height() != height || width * height != ingredients.length) {
List<SlotDisplay> ingredients = shapedRecipe.ingredients();
if (shapedRecipe.width() != width || shapedRecipe.height() != height || width * height != ingredients.size()) {
continue;
}
if (!testShapedRecipe(ingredients, inventoryGetter, gridDimensions, firstRow, height, firstCol, width)) {
Ingredient[] mirroredIngredients = new Ingredient[ingredients.length];
if (!testShapedRecipe(session, ingredients, inventoryGetter, gridDimensions, firstRow, height, firstCol, width)) {
List<SlotDisplay> mirroredIngredients = new ArrayList<>(ingredients.size());
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
mirroredIngredients[col + (row * width)] = ingredients[(width - 1 - col) + (row * width)];
mirroredIngredients.add(ingredients.get((width - 1 - col) + (row * width)));
}
}
if (Arrays.equals(ingredients, mirroredIngredients) ||
!testShapedRecipe(mirroredIngredients, inventoryGetter, gridDimensions, firstRow, height, firstCol, width)) {
if (!ingredients.equals(mirroredIngredients) ||
!testShapedRecipe(session, mirroredIngredients, inventoryGetter, gridDimensions, firstRow, height, firstCol, width)) {
continue;
}
}
} else {
GeyserShapelessRecipe data = (GeyserShapelessRecipe) recipe;
if (output != null && !data.result().equals(output)) {
if (output != null && !acceptsAsInput(session, data.result(), GeyserItemStack.from(output))) {
continue;
}
if (nonAirCount != data.ingredients().length) {
if (nonAirCount != data.ingredients().size()) {
// There is an amount of items on the crafting table that is not the same as the ingredient count so this is invalid
continue;
}
for (int i = 0; i < data.ingredients().length; i++) {
Ingredient ingredient = data.ingredients()[i];
for (int item : ingredient.getValues().getHolders()) { // FIXME
boolean inventoryHasItem = false;
// Iterate only over the crafting table to find this item
crafting:
for (int row = firstRow; row < height + firstRow; row++) {
for (int col = firstCol; col < width + firstCol; col++) {
GeyserItemStack geyserItemStack = inventoryGetter.apply(col + (row * gridDimensions) + 1);
if (geyserItemStack.isEmpty()) {
inventoryHasItem = item == 0;
if (inventoryHasItem) {
break crafting;
}
} else if (item == geyserItemStack.getJavaId()) {
inventoryHasItem = true;
break crafting;
}
for (int i = 0; i < data.ingredients().size(); i++) {
SlotDisplay slotDisplay = data.ingredients().get(i);
boolean inventoryHasItem = false;
// Iterate only over the crafting table to find this item
for (int row = firstRow; row < height + firstRow; row++) {
for (int col = firstCol; col < width + firstCol; col++) {
GeyserItemStack geyserItemStack = inventoryGetter.apply(col + (row * gridDimensions) + 1);
if (acceptsAsInput(session, slotDisplay, geyserItemStack)) {
inventoryHasItem = true;
break;
}
}
if (!inventoryHasItem) {
continue recipes;
}
}
if (!inventoryHasItem) {
continue recipes;
}
}
}
System.out.println("Found existing match for item: " + recipe);
return recipe;
}
return null;
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private static boolean testShapedRecipe(final Ingredient[] ingredients, final IntFunction<GeyserItemStack> inventoryGetter,
private static boolean testShapedRecipe(final GeyserSession session, final List<SlotDisplay> ingredients, final IntFunction<GeyserItemStack> inventoryGetter,
final int gridDimensions, final int firstRow, final int height, final int firstCol, final int width) {
int ingredientIndex = 0;
for (int row = firstRow; row < height + firstRow; row++) {
for (int col = firstCol; col < width + firstCol; col++) {
GeyserItemStack geyserItemStack = inventoryGetter.apply(col + (row * gridDimensions) + 1);
Ingredient ingredient = ingredients[ingredientIndex++];
int[] items = ingredient.getValues().getHolders(); // FIXME
if (items.length == 0) {
if (!geyserItemStack.isEmpty()) {
return false;
}
} else {
boolean inventoryHasItem = false;
for (int item : items) {
if (geyserItemStack.getJavaId() == item) {
inventoryHasItem = true;
break;
}
}
if (!inventoryHasItem) {
return false;
}
SlotDisplay slotDisplay = ingredients.get(ingredientIndex++);
if (!acceptsAsInput(session, slotDisplay, geyserItemStack)) {
return false;
}
}
}