Skip to content

Commit

Permalink
On-the-fly smithing transform recipes
Browse files Browse the repository at this point in the history
  • Loading branch information
Camotoy committed Oct 29, 2024
1 parent 05f153c commit 52679f9
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,13 @@ public ItemMapping getMapping(GeyserSession session) {
return session.getItemMappings().getMapping(this.javaId);
}

public SlotDisplay asSlotDisplay() {
if (isEmpty()) {
return new EmptySlotDisplay();
}
return new ItemStackSlotDisplay(this.getItemStack());
}

public Item asItem() {
if (isEmpty()) {
return Items.AIR;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright (c) 2024 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/

package org.geysermc.geyser.inventory.recipe;

import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.SmithingRecipeDisplay;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.SlotDisplay;

public record GeyserSmithingRecipe(SlotDisplay template,
SlotDisplay base,
SlotDisplay addition,
SlotDisplay result) implements GeyserRecipe {
public GeyserSmithingRecipe(SmithingRecipeDisplay display) {
this(display.template(), display.base(), display.addition(), display.result());
}

@Override
public boolean isShaped() {
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import com.google.gson.JsonObject;
import io.netty.channel.Channel;
import io.netty.channel.EventLoop;
import it.unimi.dsi.fastutil.Pair;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
Expand Down Expand Up @@ -77,6 +78,7 @@
import org.cloudburstmc.protocol.bedrock.data.definitions.DimensionDefinition;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.CraftingRecipeData;
import org.cloudburstmc.protocol.bedrock.packet.AvailableEntityIdentifiersPacket;
import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket;
import org.cloudburstmc.protocol.bedrock.packet.BiomeDefinitionListPacket;
Expand Down Expand Up @@ -141,6 +143,7 @@
import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.inventory.PlayerInventory;
import org.geysermc.geyser.inventory.recipe.GeyserRecipe;
import org.geysermc.geyser.inventory.recipe.GeyserSmithingRecipe;
import org.geysermc.geyser.inventory.recipe.GeyserStonecutterData;
import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.item.type.BlockItem;
Expand Down Expand Up @@ -311,7 +314,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
private final AtomicInteger itemNetId = new AtomicInteger(2);

@Setter
private ScheduledFuture<?> craftingGridFuture;
private ScheduledFuture<?> containerOutputFuture;

/**
* Stores session collision
Expand Down Expand Up @@ -447,6 +450,8 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
private final Int2ObjectMap<List<String>> javaToBedrockRecipeIds;

private final Int2ObjectMap<GeyserRecipe> craftingRecipes;
@Setter
private Pair<CraftingRecipeData, GeyserRecipe> lastCreatedRecipe = null; // TODO try to prevent sending duplicate recipes
private final AtomicInteger lastRecipeNetId;

/**
Expand All @@ -455,6 +460,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
*/
@Setter
private Int2ObjectMap<GeyserStonecutterData> stonecutterRecipes;
private final List<GeyserSmithingRecipe> smithingRecipes = new ArrayList<>();

/**
* Whether to work around 1.13's different behavior in villager trading menus.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,39 +33,44 @@
import org.geysermc.geyser.level.block.Blocks;

public class SmithingInventoryTranslator extends AbstractBlockInventoryTranslator {
public static final int TEMPLATE = 0;
public static final int INPUT = 1;
public static final int MATERIAL = 2;
public static final int OUTPUT = 3;

public SmithingInventoryTranslator() {
super(4, Blocks.SMITHING_TABLE, ContainerType.SMITHING_TABLE, UIInventoryUpdater.INSTANCE);
}

@Override
public int bedrockSlotToJava(ItemStackRequestSlotData slotInfoData) {
return switch (slotInfoData.getContainer()) {
case SMITHING_TABLE_TEMPLATE -> 0;
case SMITHING_TABLE_INPUT -> 1;
case SMITHING_TABLE_MATERIAL -> 2;
case SMITHING_TABLE_RESULT, CREATED_OUTPUT -> 3;
case SMITHING_TABLE_TEMPLATE -> TEMPLATE;
case SMITHING_TABLE_INPUT -> INPUT;
case SMITHING_TABLE_MATERIAL -> MATERIAL;
case SMITHING_TABLE_RESULT, CREATED_OUTPUT -> OUTPUT;
default -> super.bedrockSlotToJava(slotInfoData);
};
}

@Override
public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
return switch (slot) {
case 0 -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_TEMPLATE, 53);
case 1 -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_INPUT, 51);
case 2 -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_MATERIAL, 52);
case 3 -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_RESULT, 50);
case TEMPLATE -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_TEMPLATE, 53);
case INPUT -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_INPUT, 51);
case MATERIAL -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_MATERIAL, 52);
case OUTPUT -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_RESULT, 50);
default -> super.javaSlotToBedrockContainer(slot);
};
}

@Override
public int javaSlotToBedrock(int slot) {
return switch (slot) {
case 0 -> 53;
case 1 -> 51;
case 2 -> 52;
case 3 -> 50;
case TEMPLATE -> 53;
case INPUT -> 51;
case MATERIAL -> 52;
case OUTPUT -> 50;
default -> super.javaSlotToBedrock(slot);
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
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.inventory.recipe.GeyserSmithingRecipe;
import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.item.type.BedrockRequiresTagItem;
import org.geysermc.geyser.item.type.Item;
Expand Down Expand Up @@ -176,6 +177,8 @@ public void translate(GeyserSession session, ClientboundRecipeBookAddPacket pack
}
}
javaToBedrockRecipeIds.put(contents.id(), bedrockRecipeIds);
session.getSmithingRecipes().add(new GeyserSmithingRecipe(smithingRecipe));
System.out.println(new GeyserSmithingRecipe(smithingRecipe));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,25 @@
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.RecipeUnlockingRequirement;
import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.ShapedRecipeData;
import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.SmithingTransformRecipeData;
import org.cloudburstmc.protocol.bedrock.data.inventory.descriptor.ItemDescriptorWithCount;
import org.cloudburstmc.protocol.bedrock.packet.CraftingDataPacket;
import org.cloudburstmc.protocol.bedrock.packet.InventorySlotPacket;
import org.geysermc.geyser.GeyserLogger;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.inventory.recipe.GeyserShapedRecipe;
import org.geysermc.geyser.inventory.recipe.GeyserSmithingRecipe;
import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
import org.geysermc.geyser.translator.inventory.PlayerInventoryTranslator;
import org.geysermc.geyser.translator.inventory.SmithingInventoryTranslator;
import org.geysermc.geyser.translator.item.ItemTranslator;
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.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;
Expand Down Expand Up @@ -83,7 +86,11 @@ public void translate(GeyserSession session, ClientboundContainerSetSlotPacket p
return;
}

updateCraftingGrid(session, slot, packet.getItem(), inventory, translator);
if (translator instanceof SmithingInventoryTranslator) {
updateSmithingTableOutput(session, slot, packet.getItem(), inventory);
} else {
updateCraftingGrid(session, slot, packet.getItem(), inventory, translator);
}

GeyserItemStack newItem = GeyserItemStack.from(packet.getItem());
if (packet.getContainerId() == 0 && !(translator instanceof PlayerInventoryTranslator)) {
Expand Down Expand Up @@ -119,15 +126,15 @@ private static void updateCraftingGrid(GeyserSession session, int slot, ItemStac
}

// Only process the most recent crafting grid result, and cancel the previous one.
if (session.getCraftingGridFuture() != null) {
session.getCraftingGridFuture().cancel(false);
if (session.getContainerOutputFuture() != null) {
session.getContainerOutputFuture().cancel(false);
}

if (InventoryUtils.isEmpty(item)) {
return;
}

session.setCraftingGridFuture(session.scheduleInEventLoop(() -> {
session.setContainerOutputFuture(session.scheduleInEventLoop(() -> {
int offset = gridSize == 4 ? 28 : 32;
int gridDimensions = gridSize == 4 ? 2 : 3;
int firstRow = -1, height = -1;
Expand Down Expand Up @@ -172,7 +179,7 @@ private static void updateCraftingGrid(GeyserSession session, int slot, ItemStac
for (int col = firstCol; col < width + firstCol; col++) {
GeyserItemStack geyserItemStack = inventory.getItem(col + (row * gridDimensions) + 1);
ingredients[index] = geyserItemStack.getItemData(session);
javaIngredients.add(geyserItemStack.isEmpty() ? new EmptySlotDisplay() : new ItemStackSlotDisplay(geyserItemStack.getItemStack()));
javaIngredients.add(geyserItemStack.asSlotDisplay());

InventorySlotPacket slotPacket = new InventorySlotPacket();
slotPacket.setContainerId(ContainerId.UI);
Expand Down Expand Up @@ -216,4 +223,74 @@ private static void updateCraftingGrid(GeyserSession session, int slot, ItemStac
}
}, 150, TimeUnit.MILLISECONDS));
}

private static void updateSmithingTableOutput(GeyserSession session, int slot, ItemStack output, Inventory inventory) {
if (slot != SmithingInventoryTranslator.OUTPUT) {
return;
}

// Only process the most recent output result, and cancel the previous one.
if (session.getContainerOutputFuture() != null) {
session.getContainerOutputFuture().cancel(false);
}

if (InventoryUtils.isEmpty(output)) {
return;
}

session.setContainerOutputFuture(session.scheduleInEventLoop(() -> {
GeyserItemStack template = inventory.getItem(SmithingInventoryTranslator.TEMPLATE);
if (template.asItem() != Items.NETHERITE_UPGRADE_SMITHING_TEMPLATE) {
// Technically we should probably also do this for custom items, but last I checked Bedrock doesn't even support that.
return;
}

GeyserItemStack input = inventory.getItem(SmithingInventoryTranslator.INPUT);
GeyserItemStack material = inventory.getItem(SmithingInventoryTranslator.MATERIAL);
GeyserItemStack geyserOutput = GeyserItemStack.from(output);

for (GeyserSmithingRecipe recipe : session.getSmithingRecipes()) {
if (InventoryUtils.acceptsAsInput(session, recipe.result(), geyserOutput)
&& InventoryUtils.acceptsAsInput(session, recipe.base(), input)
&& InventoryUtils.acceptsAsInput(session, recipe.addition(), material)
&& InventoryUtils.acceptsAsInput(session, recipe.template(), template)) {
// The client already recognizes this item.
return;
}
}

session.getSmithingRecipes().add(new GeyserSmithingRecipe(
template.asSlotDisplay(),
input.asSlotDisplay(),
material.asSlotDisplay(),
new ItemStackSlotDisplay(output)
));

UUID uuid = UUID.randomUUID();

ItemData bedrockAddition = ItemTranslator.translateToBedrock(session, material.getItemStack());

CraftingDataPacket craftPacket = new CraftingDataPacket();
craftPacket.getCraftingData().add(SmithingTransformRecipeData.of(
uuid.toString(),
ItemDescriptorWithCount.fromItem(ItemTranslator.translateToBedrock(session, template.getItemStack())),
ItemDescriptorWithCount.fromItem(ItemTranslator.translateToBedrock(session, input.getItemStack())),
ItemDescriptorWithCount.fromItem(bedrockAddition),
ItemTranslator.translateToBedrock(session, output),
"smithing_table",
session.getLastRecipeNetId().incrementAndGet()
));
craftPacket.setCleanRecipes(false);
session.sendUpstreamPacket(craftPacket);

// Just set one of the slots to air, then right back to its proper item.
InventorySlotPacket slotPacket = new InventorySlotPacket();
slotPacket.setContainerId(ContainerId.UI);
slotPacket.setSlot(session.getInventoryTranslator().javaSlotToBedrock(SmithingInventoryTranslator.MATERIAL));
slotPacket.setItem(ItemData.AIR);
session.sendUpstreamPacket(slotPacket);

session.getInventoryTranslator().updateSlot(session, inventory, SmithingInventoryTranslator.MATERIAL);
}, 150, TimeUnit.MILLISECONDS));
}
}

0 comments on commit 52679f9

Please sign in to comment.