From 8738f991ff6532416054cfef5b19918b9f7fa4d4 Mon Sep 17 00:00:00 2001 From: YHDiamond Date: Wed, 19 Jun 2024 20:15:27 -0400 Subject: [PATCH] Proof of concept for book cloning fix --- .../geyser/inventory/click/ClickPlan.java | 8 +- .../inventory/InventoryTranslator.java | 125 ++++++++++++++++-- .../geysermc/geyser/util/InventoryUtils.java | 4 + 3 files changed, 125 insertions(+), 12 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/inventory/click/ClickPlan.java b/core/src/main/java/org/geysermc/geyser/inventory/click/ClickPlan.java index 53b02ef88..e54c63976 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/click/ClickPlan.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/click/ClickPlan.java @@ -58,11 +58,17 @@ public final class ClickPlan { private final InventoryTranslator translator; private final Inventory inventory; private final int gridSize; + private final boolean handleBookRecipe; public ClickPlan(GeyserSession session, InventoryTranslator translator, Inventory inventory) { + this(session, translator, inventory, false); + } + + public ClickPlan(GeyserSession session, InventoryTranslator translator, Inventory inventory, boolean handleBookRecipe) { this.session = session; this.translator = translator; this.inventory = inventory; + this.handleBookRecipe = handleBookRecipe; this.simulatedItems = new Int2ObjectOpenHashMap<>(inventory.getSize()); this.changedItems = null; @@ -376,7 +382,7 @@ public final class ClickPlan { for (int i = 0; i < gridSize; i++) { final int slot = i + 1; GeyserItemStack item = getItem(slot); - if (!item.isEmpty()) { + if (!item.isEmpty() && (!handleBookRecipe || item.getJavaId() != session.getItemMappings().getStoredItems().writtenBook().getJavaItem().javaId())) { // These changes should be broadcasted to the server sub(slot, item, crafted); } diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/InventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/InventoryTranslator.java index 4c426b410..5db3c3b8d 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/InventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/InventoryTranslator.java @@ -453,35 +453,46 @@ public abstract class InventoryTranslator { ClickPlan plan = new ClickPlan(session, this, inventory); // Track all the crafting table slots to report back the contents of the slots after crafting IntSet affectedSlots = new IntOpenHashSet(); + boolean reject = false; for (ItemStackRequestAction action : request.getActions()) { switch (action.getType()) { case CRAFT_RECIPE: { if (craftState != CraftState.START) { - return rejectRequest(request); + reject = true; + break; } craftState = CraftState.RECIPE_ID; + + if (((RecipeItemStackRequestAction) action).getRecipeNetworkId() == InventoryUtils.BOOK_CLONING_RECIPE_ID) { + // Book copying needs to be handled differently + // There's a leftover item + return translateBookCopyCraftingRequest(session, inventory, request); + } break; } case CRAFT_RESULTS_DEPRECATED: { CraftResultsDeprecatedAction deprecatedCraftAction = (CraftResultsDeprecatedAction) action; if (craftState != CraftState.RECIPE_ID) { - return rejectRequest(request); + reject = true; + break; } craftState = CraftState.DEPRECATED; if (deprecatedCraftAction.getResultItems().length != 1) { - return rejectRequest(request); + reject = true; + break; } resultSize = deprecatedCraftAction.getResultItems()[0].getCount(); timesCrafted = deprecatedCraftAction.getTimesCrafted(); if (resultSize <= 0 || timesCrafted <= 0) { - return rejectRequest(request); + reject = true; } break; } case CONSUME: { if (craftState != CraftState.DEPRECATED && craftState != CraftState.INGREDIENTS) { - return rejectRequest(request); + reject = true; + break; } craftState = CraftState.INGREDIENTS; affectedSlots.add(bedrockSlotToJava(((ConsumeAction) action).getSource())); @@ -491,15 +502,18 @@ public abstract class InventoryTranslator { case PLACE: { TransferItemStackRequestAction transferAction = (TransferItemStackRequestAction) action; if (craftState != CraftState.INGREDIENTS && craftState != CraftState.TRANSFER) { - return rejectRequest(request); + reject = true; + break; } craftState = CraftState.TRANSFER; if (transferAction.getSource().getContainer() != ContainerSlotType.CREATED_OUTPUT) { - return rejectRequest(request); + reject = true; + break; } if (transferAction.getCount() <= 0) { - return rejectRequest(request); + reject = true; + break; } int sourceSlot = bedrockSlotToJava(transferAction.getSource()); @@ -511,7 +525,8 @@ public abstract class InventoryTranslator { } else { if (leftover != 0) { if (transferAction.getCount() > leftover) { - return rejectRequest(request); + reject = true; + break; } if (transferAction.getCount() == leftover) { plan.add(Click.LEFT, destSlot); @@ -537,7 +552,8 @@ public abstract class InventoryTranslator { GeyserItemStack cursor = session.getPlayerInventory().getCursor(); int tempSlot = findTempSlot(inventory, cursor, true, sourceSlot, destSlot); if (tempSlot == -1) { - return rejectRequest(request); + reject = true; + break; } plan.add(Click.LEFT, tempSlot); //place cursor into temp slot @@ -559,14 +575,101 @@ public abstract class InventoryTranslator { break; } default: - return rejectRequest(request); + reject = true; } + + } + if (reject) { + return rejectRequest(request); } plan.execute(false); affectedSlots.addAll(plan.getAffectedSlots()); return acceptRequest(request, makeContainerEntries(session, inventory, affectedSlots)); } + /** + * Book copying is unique in that there is an item remaining in the crafting table when done. + */ + public ItemStackResponse translateBookCopyCraftingRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { + CraftState craftState = CraftState.START; + boolean newBookHandled = false; + + ClickPlan plan = new ClickPlan(session, this, inventory, true); + for (ItemStackRequestAction action : request.getActions()) { + switch (action.getType()) { + case CRAFT_RECIPE -> { + if (craftState != CraftState.START) { + return rejectRequest(request); + } + craftState = CraftState.RECIPE_ID; + } + case CRAFT_RESULTS_DEPRECATED -> { + CraftResultsDeprecatedAction deprecatedCraftAction = (CraftResultsDeprecatedAction) action; + if (craftState != CraftState.RECIPE_ID) { + return rejectRequest(request); + } + craftState = CraftState.DEPRECATED; + + if (deprecatedCraftAction.getResultItems().length != 2) { + // Crafted item and old book + return rejectRequest(request); + } + int resultSize = deprecatedCraftAction.getResultItems()[0].getCount(); + int timesCrafted = deprecatedCraftAction.getTimesCrafted(); + if (resultSize != 1 || timesCrafted != 1) { + return rejectRequest(request); + } + } + case CONSUME -> { + // Ignore I guess + } + case CREATE -> { + // After the proper book is created this is called + } + case TAKE, PLACE -> { + TransferItemStackRequestAction transferAction = (TransferItemStackRequestAction) action; + if (craftState != CraftState.DEPRECATED) { + return rejectRequest(request); + } + + if (newBookHandled) { + // Don't let this execute for the old book and keep it in its old slot + // Bedrock wants to move it to the inventory; don't let it + continue; + } + + if (transferAction.getSource().getContainer() != ContainerSlotType.CREATED_OUTPUT) { + return rejectRequest(request); + } + if (transferAction.getCount() != 1) { + return rejectRequest(request); + } + + int sourceSlot = bedrockSlotToJava(transferAction.getSource()); + int destSlot = bedrockSlotToJava(transferAction.getDestination()); + + // Books are pretty simple in this regard - we'll yeet the written book when we execute + // the click plan, but otherwise a book isn't stackable so there aren't many options for it + if (isCursor(transferAction.getDestination())) { + plan.add(Click.LEFT, sourceSlot); + } else { + plan.add(Click.LEFT, sourceSlot); + plan.add(Click.LEFT, destSlot); + } + + newBookHandled = true; + } + default -> { + return rejectRequest(request); + } + } + } + + plan.execute(false); + return acceptRequest(request, makeContainerEntries(session, inventory, plan.getAffectedSlots())); + } + + public ItemStackResponse translateAutoCraftingRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { final int gridSize = getGridSize(); if (gridSize == -1) { diff --git a/core/src/main/java/org/geysermc/geyser/util/InventoryUtils.java b/core/src/main/java/org/geysermc/geyser/util/InventoryUtils.java index 48ade52e2..0286461a5 100644 --- a/core/src/main/java/org/geysermc/geyser/util/InventoryUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/InventoryUtils.java @@ -74,6 +74,10 @@ public class InventoryUtils { * each recipe needs a unique network ID (or else in .200 the client crashes). */ public static int LAST_RECIPE_NET_ID; + /** + * Book cloning recipe ID; stored separately as its recipe works differently from others. + */ + public static final int BOOK_CLONING_RECIPE_ID = 278; public static final ItemStack REFRESH_ITEM = new ItemStack(1, 127, new DataComponents(new HashMap<>()));