Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Book cloning fix #4784

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

package org.geysermc.geyser.inventory.click;

import org.geysermc.geyser.item.Items;
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerActionType;
import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
Expand Down Expand Up @@ -58,11 +59,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;
Konicai marked this conversation as resolved.
Show resolved Hide resolved

this.simulatedItems = new Int2ObjectOpenHashMap<>(inventory.getSize());
this.changedItems = null;
Expand Down Expand Up @@ -376,7 +383,7 @@ private void reduceCraftingGrid(boolean makeAll) {
for (int i = 0; i < gridSize; i++) {
final int slot = i + 1;
GeyserItemStack item = getItem(slot);
if (!item.isEmpty()) {
if (!item.isEmpty() && item.asItem() == Items.WRITTEN_BOOK) {
// These changes should be broadcasted to the server
sub(slot, item, crafted);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -453,35 +453,46 @@ public ItemStackResponse translateCraftingRequest(GeyserSession session, Invento
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;
Konicai marked this conversation as resolved.
Show resolved Hide resolved
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
YHDiamond marked this conversation as resolved.
Show resolved Hide resolved
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()));
Expand All @@ -491,15 +502,18 @@ public ItemStackResponse translateCraftingRequest(GeyserSession session, Invento
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());
Expand All @@ -511,7 +525,8 @@ public ItemStackResponse translateCraftingRequest(GeyserSession session, Invento
} else {
if (leftover != 0) {
if (transferAction.getCount() > leftover) {
return rejectRequest(request);
reject = true;
break;
}
if (transferAction.getCount() == leftover) {
plan.add(Click.LEFT, destSlot);
Expand All @@ -537,7 +552,8 @@ public ItemStackResponse translateCraftingRequest(GeyserSession session, Invento
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
Expand All @@ -559,14 +575,101 @@ public ItemStackResponse translateCraftingRequest(GeyserSession session, Invento
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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can't be hardcoded, as it could differ between two sessions on standalone working with different recipes.


public static final ItemStack REFRESH_ITEM = new ItemStack(1, 127, new DataComponents(new HashMap<>()));

Expand Down