3
0
Mirror von https://github.com/GeyserMC/Geyser.git synchronisiert 2024-12-26 00:00:41 +01:00

On-the-fly smithing transform recipes

Dieser Commit ist enthalten in:
Camotoy 2024-10-29 01:33:57 -04:00
Ursprung 05f153c941
Commit 52679f9f81
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: 7EEFB66FE798081F
6 geänderte Dateien mit 160 neuen und 19 gelöschten Zeilen

Datei anzeigen

@ -182,6 +182,13 @@ public class GeyserItemStack {
return session.getItemMappings().getMapping(this.javaId); return session.getItemMappings().getMapping(this.javaId);
} }
public SlotDisplay asSlotDisplay() {
if (isEmpty()) {
return new EmptySlotDisplay();
}
return new ItemStackSlotDisplay(this.getItemStack());
}
public Item asItem() { public Item asItem() {
if (isEmpty()) { if (isEmpty()) {
return Items.AIR; return Items.AIR;

Datei anzeigen

@ -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;
}
}

Datei anzeigen

@ -29,6 +29,7 @@ import com.google.gson.Gson;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.EventLoop; import io.netty.channel.EventLoop;
import it.unimi.dsi.fastutil.Pair;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntMap;
@ -77,6 +78,7 @@ import org.cloudburstmc.protocol.bedrock.data.command.SoftEnumUpdateType;
import org.cloudburstmc.protocol.bedrock.data.definitions.DimensionDefinition; import org.cloudburstmc.protocol.bedrock.data.definitions.DimensionDefinition;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag; import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData; 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.AvailableEntityIdentifiersPacket;
import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket; import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket;
import org.cloudburstmc.protocol.bedrock.packet.BiomeDefinitionListPacket; import org.cloudburstmc.protocol.bedrock.packet.BiomeDefinitionListPacket;
@ -141,6 +143,7 @@ import org.geysermc.geyser.impl.camera.GeyserCameraData;
import org.geysermc.geyser.inventory.Inventory; import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.inventory.PlayerInventory; import org.geysermc.geyser.inventory.PlayerInventory;
import org.geysermc.geyser.inventory.recipe.GeyserRecipe; 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.inventory.recipe.GeyserStonecutterData;
import org.geysermc.geyser.item.Items; import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.item.type.BlockItem; import org.geysermc.geyser.item.type.BlockItem;
@ -311,7 +314,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
private final AtomicInteger itemNetId = new AtomicInteger(2); private final AtomicInteger itemNetId = new AtomicInteger(2);
@Setter @Setter
private ScheduledFuture<?> craftingGridFuture; private ScheduledFuture<?> containerOutputFuture;
/** /**
* Stores session collision * Stores session collision
@ -447,6 +450,8 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
private final Int2ObjectMap<List<String>> javaToBedrockRecipeIds; private final Int2ObjectMap<List<String>> javaToBedrockRecipeIds;
private final Int2ObjectMap<GeyserRecipe> craftingRecipes; private final Int2ObjectMap<GeyserRecipe> craftingRecipes;
@Setter
private Pair<CraftingRecipeData, GeyserRecipe> lastCreatedRecipe = null; // TODO try to prevent sending duplicate recipes
private final AtomicInteger lastRecipeNetId; private final AtomicInteger lastRecipeNetId;
/** /**
@ -455,6 +460,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
*/ */
@Setter @Setter
private Int2ObjectMap<GeyserStonecutterData> stonecutterRecipes; private Int2ObjectMap<GeyserStonecutterData> stonecutterRecipes;
private final List<GeyserSmithingRecipe> smithingRecipes = new ArrayList<>();
/** /**
* Whether to work around 1.13's different behavior in villager trading menus. * Whether to work around 1.13's different behavior in villager trading menus.

Datei anzeigen

@ -33,6 +33,11 @@ import org.geysermc.geyser.inventory.updater.UIInventoryUpdater;
import org.geysermc.geyser.level.block.Blocks; import org.geysermc.geyser.level.block.Blocks;
public class SmithingInventoryTranslator extends AbstractBlockInventoryTranslator { 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() { public SmithingInventoryTranslator() {
super(4, Blocks.SMITHING_TABLE, ContainerType.SMITHING_TABLE, UIInventoryUpdater.INSTANCE); super(4, Blocks.SMITHING_TABLE, ContainerType.SMITHING_TABLE, UIInventoryUpdater.INSTANCE);
} }
@ -40,10 +45,10 @@ public class SmithingInventoryTranslator extends AbstractBlockInventoryTranslato
@Override @Override
public int bedrockSlotToJava(ItemStackRequestSlotData slotInfoData) { public int bedrockSlotToJava(ItemStackRequestSlotData slotInfoData) {
return switch (slotInfoData.getContainer()) { return switch (slotInfoData.getContainer()) {
case SMITHING_TABLE_TEMPLATE -> 0; case SMITHING_TABLE_TEMPLATE -> TEMPLATE;
case SMITHING_TABLE_INPUT -> 1; case SMITHING_TABLE_INPUT -> INPUT;
case SMITHING_TABLE_MATERIAL -> 2; case SMITHING_TABLE_MATERIAL -> MATERIAL;
case SMITHING_TABLE_RESULT, CREATED_OUTPUT -> 3; case SMITHING_TABLE_RESULT, CREATED_OUTPUT -> OUTPUT;
default -> super.bedrockSlotToJava(slotInfoData); default -> super.bedrockSlotToJava(slotInfoData);
}; };
} }
@ -51,10 +56,10 @@ public class SmithingInventoryTranslator extends AbstractBlockInventoryTranslato
@Override @Override
public BedrockContainerSlot javaSlotToBedrockContainer(int slot) { public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
return switch (slot) { return switch (slot) {
case 0 -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_TEMPLATE, 53); case TEMPLATE -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_TEMPLATE, 53);
case 1 -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_INPUT, 51); case INPUT -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_INPUT, 51);
case 2 -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_MATERIAL, 52); case MATERIAL -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_MATERIAL, 52);
case 3 -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_RESULT, 50); case OUTPUT -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_RESULT, 50);
default -> super.javaSlotToBedrockContainer(slot); default -> super.javaSlotToBedrockContainer(slot);
}; };
} }
@ -62,10 +67,10 @@ public class SmithingInventoryTranslator extends AbstractBlockInventoryTranslato
@Override @Override
public int javaSlotToBedrock(int slot) { public int javaSlotToBedrock(int slot) {
return switch (slot) { return switch (slot) {
case 0 -> 53; case TEMPLATE -> 53;
case 1 -> 51; case INPUT -> 51;
case 2 -> 52; case MATERIAL -> 52;
case 3 -> 50; case OUTPUT -> 50;
default -> super.javaSlotToBedrock(slot); default -> super.javaSlotToBedrock(slot);
}; };
} }

Datei anzeigen

@ -42,6 +42,7 @@ import org.cloudburstmc.protocol.bedrock.packet.UnlockedRecipesPacket;
import org.geysermc.geyser.inventory.recipe.GeyserRecipe; import org.geysermc.geyser.inventory.recipe.GeyserRecipe;
import org.geysermc.geyser.inventory.recipe.GeyserShapedRecipe; import org.geysermc.geyser.inventory.recipe.GeyserShapedRecipe;
import org.geysermc.geyser.inventory.recipe.GeyserShapelessRecipe; 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.Items;
import org.geysermc.geyser.item.type.BedrockRequiresTagItem; import org.geysermc.geyser.item.type.BedrockRequiresTagItem;
import org.geysermc.geyser.item.type.Item; import org.geysermc.geyser.item.type.Item;
@ -176,6 +177,8 @@ public class JavaRecipeBookAddTranslator extends PacketTranslator<ClientboundRec
} }
} }
javaToBedrockRecipeIds.put(contents.id(), bedrockRecipeIds); javaToBedrockRecipeIds.put(contents.id(), bedrockRecipeIds);
session.getSmithingRecipes().add(new GeyserSmithingRecipe(smithingRecipe));
System.out.println(new GeyserSmithingRecipe(smithingRecipe));
} }
} }
} }

Datei anzeigen

@ -29,6 +29,7 @@ import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerId;
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData; 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.RecipeUnlockingRequirement;
import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.ShapedRecipeData; 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.data.inventory.descriptor.ItemDescriptorWithCount;
import org.cloudburstmc.protocol.bedrock.packet.CraftingDataPacket; import org.cloudburstmc.protocol.bedrock.packet.CraftingDataPacket;
import org.cloudburstmc.protocol.bedrock.packet.InventorySlotPacket; import org.cloudburstmc.protocol.bedrock.packet.InventorySlotPacket;
@ -36,15 +37,17 @@ import org.geysermc.geyser.GeyserLogger;
import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.inventory.Inventory; import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.inventory.recipe.GeyserShapedRecipe; 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.session.GeyserSession;
import org.geysermc.geyser.translator.inventory.InventoryTranslator; import org.geysermc.geyser.translator.inventory.InventoryTranslator;
import org.geysermc.geyser.translator.inventory.PlayerInventoryTranslator; 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.item.ItemTranslator;
import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator; import org.geysermc.geyser.translator.protocol.Translator;
import org.geysermc.geyser.util.InventoryUtils; import org.geysermc.geyser.util.InventoryUtils;
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack; 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.ItemStackSlotDisplay;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.SlotDisplay; import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.SlotDisplay;
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.inventory.ClientboundContainerSetSlotPacket; import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.inventory.ClientboundContainerSetSlotPacket;
@ -83,7 +86,11 @@ public class JavaContainerSetSlotTranslator extends PacketTranslator<Clientbound
return; 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()); GeyserItemStack newItem = GeyserItemStack.from(packet.getItem());
if (packet.getContainerId() == 0 && !(translator instanceof PlayerInventoryTranslator)) { if (packet.getContainerId() == 0 && !(translator instanceof PlayerInventoryTranslator)) {
@ -119,15 +126,15 @@ public class JavaContainerSetSlotTranslator extends PacketTranslator<Clientbound
} }
// Only process the most recent crafting grid result, and cancel the previous one. // Only process the most recent crafting grid result, and cancel the previous one.
if (session.getCraftingGridFuture() != null) { if (session.getContainerOutputFuture() != null) {
session.getCraftingGridFuture().cancel(false); session.getContainerOutputFuture().cancel(false);
} }
if (InventoryUtils.isEmpty(item)) { if (InventoryUtils.isEmpty(item)) {
return; return;
} }
session.setCraftingGridFuture(session.scheduleInEventLoop(() -> { session.setContainerOutputFuture(session.scheduleInEventLoop(() -> {
int offset = gridSize == 4 ? 28 : 32; int offset = gridSize == 4 ? 28 : 32;
int gridDimensions = gridSize == 4 ? 2 : 3; int gridDimensions = gridSize == 4 ? 2 : 3;
int firstRow = -1, height = -1; int firstRow = -1, height = -1;
@ -172,7 +179,7 @@ public class JavaContainerSetSlotTranslator extends PacketTranslator<Clientbound
for (int col = firstCol; col < width + firstCol; col++) { for (int col = firstCol; col < width + firstCol; col++) {
GeyserItemStack geyserItemStack = inventory.getItem(col + (row * gridDimensions) + 1); GeyserItemStack geyserItemStack = inventory.getItem(col + (row * gridDimensions) + 1);
ingredients[index] = geyserItemStack.getItemData(session); ingredients[index] = geyserItemStack.getItemData(session);
javaIngredients.add(geyserItemStack.isEmpty() ? new EmptySlotDisplay() : new ItemStackSlotDisplay(geyserItemStack.getItemStack())); javaIngredients.add(geyserItemStack.asSlotDisplay());
InventorySlotPacket slotPacket = new InventorySlotPacket(); InventorySlotPacket slotPacket = new InventorySlotPacket();
slotPacket.setContainerId(ContainerId.UI); slotPacket.setContainerId(ContainerId.UI);
@ -216,4 +223,74 @@ public class JavaContainerSetSlotTranslator extends PacketTranslator<Clientbound
} }
}, 150, TimeUnit.MILLISECONDS)); }, 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));
}
} }