From 2f42a4c6308a5b736d0a40425642ea896c1205d0 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 31 Mar 2021 14:06:05 -0400 Subject: [PATCH] Add furnace minecart item translation (#2003) Conveniently enough, the minecart furnace icon still exists in the vanilla Bedrock Edition game (thanks to Kastle for this discovery). With this and the translation string still being present, we can add the item into the game with only minor issues. --- .../configuration/GeyserConfiguration.java | 2 + .../GeyserJacksonConfiguration.java | 3 + .../network/UpstreamPacketHandler.java | 7 ++ .../network/session/GeyserSession.java | 6 ++ .../translators/item/ItemRegistry.java | 73 ++++++++++++++++++- connector/src/main/resources/config.yml | 6 ++ 6 files changed, 95 insertions(+), 2 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java b/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java index 6052bd283..0d879c401 100644 --- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java +++ b/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java @@ -81,6 +81,8 @@ public interface GeyserConfiguration { Path getFloodgateKeyPath(); + boolean isAddNonBedrockItems(); + boolean isAboveBedrockNetherBuilding(); boolean isCacheChunks(); diff --git a/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java b/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java index 70aa3ff5d..cdc0ad239 100644 --- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java +++ b/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java @@ -115,6 +115,9 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration @JsonProperty("allow-custom-skulls") private boolean allowCustomSkulls = true; + @JsonProperty("add-non-bedrock-items") + private boolean addNonBedrockItems = true; + @JsonProperty("above-bedrock-nether-building") private boolean aboveBedrockNetherBuilding = false; diff --git a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java index c85bb773b..a30de4d07 100644 --- a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java @@ -27,6 +27,7 @@ package org.geysermc.connector.network; import com.nukkitx.protocol.bedrock.BedrockPacket; import com.nukkitx.protocol.bedrock.BedrockPacketCodec; +import com.nukkitx.protocol.bedrock.data.ExperimentData; import com.nukkitx.protocol.bedrock.data.ResourcePackType; import com.nukkitx.protocol.bedrock.packet.*; import com.nukkitx.protocol.bedrock.v428.Bedrock_v428; @@ -36,6 +37,7 @@ import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.cache.AdvancementsCache; import org.geysermc.connector.network.translators.PacketTranslatorRegistry; +import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.network.translators.world.block.BlockTranslator1_16_100; import org.geysermc.connector.network.translators.world.block.BlockTranslator1_16_210; import org.geysermc.connector.utils.*; @@ -133,6 +135,11 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { stackPacket.getResourcePacks().add(new ResourcePackStackPacket.Entry(header.getUuid().toString(), header.getVersionString(), "")); } + if (ItemRegistry.FURNACE_MINECART_DATA != null) { + // Allow custom items to work + stackPacket.getExperiments().add(new ExperimentData("data_driven_items", true)); + } + session.sendUpstreamPacket(stackPacket); break; diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index de6fb94b5..436b6db11 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -464,6 +464,12 @@ public class GeyserSession implements CommandSender { // Set the hardcoded shield ID to the ID we just defined in StartGamePacket upstream.getSession().getHardcodedBlockingId().set(ItemRegistry.SHIELD.getBedrockId()); + if (ItemRegistry.FURNACE_MINECART_DATA != null) { + ItemComponentPacket componentPacket = new ItemComponentPacket(); + componentPacket.getItems().add(ItemRegistry.FURNACE_MINECART_DATA); + upstream.sendPacket(componentPacket); + } + ChunkUtils.sendEmptyChunks(this, playerEntity.getPosition().toInt(), 0, false); BiomeDefinitionListPacket biomeDefinitionListPacket = new BiomeDefinitionListPacket(); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java index c865a162a..be190f6ff 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java @@ -28,14 +28,19 @@ package org.geysermc.connector.network.translators.item; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.google.common.collect.ImmutableSet; import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtMapBuilder; +import com.nukkitx.nbt.NbtType; import com.nukkitx.nbt.NbtUtils; +import com.nukkitx.protocol.bedrock.data.inventory.ComponentItemData; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.packet.StartGamePacket; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.utils.FileUtils; import org.geysermc.connector.utils.LanguageUtils; @@ -55,8 +60,7 @@ public class ItemRegistry { /** * A list of all identifiers that only exist on Java. Used to prevent creative items from becoming these unintentionally. */ - private static final List JAVA_ONLY_ITEMS = Arrays.asList("minecraft:spectral_arrow", "minecraft:debug_stick", - "minecraft:knowledge_book", "minecraft:tipped_arrow", "minecraft:furnace_minecart"); + private static final Set JAVA_ONLY_ITEMS; public static final ItemData[] CREATIVE_ITEMS; @@ -107,6 +111,11 @@ public class ItemRegistry { public static int BARRIER_INDEX = 0; + /** + * Stores the properties and data of the "custom" furnace minecart item. + */ + public static final ComponentItemData FURNACE_MINECART_DATA; + public static void init() { // no-op } @@ -150,9 +159,16 @@ public class ItemRegistry { } int itemIndex = 0; + int javaFurnaceMinecartId = 0; + boolean usingFurnaceMinecart = GeyserConnector.getInstance().getConfig().isAddNonBedrockItems(); Iterator> iterator = items.fields(); while (iterator.hasNext()) { Map.Entry entry = iterator.next(); + if (usingFurnaceMinecart && entry.getKey().equals("minecraft:furnace_minecart")) { + javaFurnaceMinecartId = itemIndex; + itemIndex++; + continue; + } int bedrockId = entry.getValue().get("bedrock_id").intValue(); String bedrockIdentifier = bedrockIdToIdentifier.get(bedrockId); if (bedrockIdentifier == null) { @@ -224,6 +240,9 @@ public class ItemRegistry { itemIndex++; } + itemNames.add("minecraft:furnace_minecart"); + itemNames.add("minecraft:spectral_arrow"); + if (lodestoneCompassId == 0) { throw new RuntimeException("Lodestone compass not found in item palette!"); } @@ -248,9 +267,59 @@ public class ItemRegistry { ItemData item = getBedrockItemFromJson(itemNode); creativeItems.add(ItemData.fromNet(netId++, item.getId(), item.getDamage(), item.getCount(), item.getTag())); } + + if (usingFurnaceMinecart) { + // Add the furnace minecart as an item + int furnaceMinecartId = ITEMS.size() + 1; + + ITEMS.add(new StartGamePacket.ItemEntry("geysermc:furnace_minecart", (short) furnaceMinecartId, true)); + ITEM_ENTRIES.put(javaFurnaceMinecartId, new ItemEntry("minecraft:furnace_minecart", "geysermc:furnace_minecart", javaFurnaceMinecartId, + furnaceMinecartId, 0, false, 1)); + creativeItems.add(ItemData.fromNet(netId, furnaceMinecartId, (short) 0, 1, null)); + + NbtMapBuilder builder = NbtMap.builder(); + builder.putString("name", "geysermc:furnace_minecart") + .putInt("id", furnaceMinecartId); + + NbtMapBuilder componentBuilder = NbtMap.builder(); + // Conveniently, as of 1.16.200, the furnace minecart has a texture AND translation string already. + componentBuilder.putCompound("minecraft:icon", NbtMap.builder().putString("texture", "minecart_furnace").build()); + componentBuilder.putCompound("minecraft:display_name", NbtMap.builder().putString("value", "item.minecartFurnace.name").build()); + + // Indicate that the arm animation should play on rails + List useOnTag = Collections.singletonList(NbtMap.builder().putString("tags", "q.any_tag('rail')").build()); + componentBuilder.putCompound("minecraft:entity_placer", NbtMap.builder() + .putList("dispense_on", NbtType.COMPOUND, useOnTag) + .putString("entity", "minecraft:minecart") + .putList("use_on", NbtType.COMPOUND, useOnTag) + .build()); + + NbtMapBuilder itemProperties = NbtMap.builder(); + // We always want to allow offhand usage when we can - matches Java Edition + itemProperties.putBoolean("allow_off_hand", true); + itemProperties.putBoolean("hand_equipped", false); + itemProperties.putInt("max_stack_size", 1); + itemProperties.putString("creative_group", "itemGroup.name.minecart"); + itemProperties.putInt("creative_category", 4); // 4 - "Items" + + componentBuilder.putCompound("item_properties", itemProperties.build()); + builder.putCompound("components", componentBuilder.build()); + FURNACE_MINECART_DATA = new ComponentItemData("geysermc:furnace_minecart", builder.build()); + } else { + FURNACE_MINECART_DATA = null; + } + CREATIVE_ITEMS = creativeItems.toArray(new ItemData[0]); ITEM_NAMES = itemNames.toArray(new String[0]); + + Set javaOnlyItems = new ObjectOpenHashSet<>(); + Collections.addAll(javaOnlyItems, "minecraft:spectral_arrow", "minecraft:debug_stick", + "minecraft:knowledge_book", "minecraft:tipped_arrow"); + if (!usingFurnaceMinecart) { + javaOnlyItems.add("minecraft:furnace_minecart"); + } + JAVA_ONLY_ITEMS = ImmutableSet.copyOf(javaOnlyItems); } /** diff --git a/connector/src/main/resources/config.yml b/connector/src/main/resources/config.yml index ce202a3c1..847eded1a 100644 --- a/connector/src/main/resources/config.yml +++ b/connector/src/main/resources/config.yml @@ -139,6 +139,12 @@ cache-images: 0 # Allows custom skulls to be displayed. Keeping them enabled may cause a performance decrease on older/weaker devices. allow-custom-skulls: true +# Whether to add (at this time, only) the furnace minecart as a separate item in the game, which normally does not exist in Bedrock Edition. +# This should only need to be disabled if using a proxy that does not use the "transfer packet" style of server switching. +# If this is disabled, furnace minecart items will be mapped to hopper minecart items. +# This option requires a restart of Geyser in order to change its setting. +add-non-bedrock-items: true + # Bedrock prevents building and displaying blocks above Y127 in the Nether - # enabling this config option works around that by changing the Nether dimension ID # to the End ID. The main downside to this is that the sky will resemble that of