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