Skip to content

Commit

Permalink
Add furnace minecart item translation (GeyserMC#2003)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Camotoy authored Mar 31, 2021
1 parent 3a4b1e4 commit 2f42a4c
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ public interface GeyserConfiguration {

Path getFloodgateKeyPath();

boolean isAddNonBedrockItems();

boolean isAboveBedrockNetherBuilding();

boolean isCacheChunks();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

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;
Expand All @@ -36,6 +37,7 @@
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.*;
Expand Down Expand Up @@ -133,6 +135,11 @@ public boolean handle(ResourcePackClientResponsePacket packet) {
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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,12 @@ public void connect() {
// 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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,19 @@
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;
Expand All @@ -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<String> JAVA_ONLY_ITEMS = Arrays.asList("minecraft:spectral_arrow", "minecraft:debug_stick",
"minecraft:knowledge_book", "minecraft:tipped_arrow", "minecraft:furnace_minecart");
private static final Set<String> JAVA_ONLY_ITEMS;

public static final ItemData[] CREATIVE_ITEMS;

Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -150,9 +159,16 @@ public static void init() {
}

int itemIndex = 0;
int javaFurnaceMinecartId = 0;
boolean usingFurnaceMinecart = GeyserConnector.getInstance().getConfig().isAddNonBedrockItems();
Iterator<Map.Entry<String, JsonNode>> iterator = items.fields();
while (iterator.hasNext()) {
Map.Entry<String, JsonNode> 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) {
Expand Down Expand Up @@ -224,6 +240,9 @@ public static void init() {
itemIndex++;
}

itemNames.add("minecraft:furnace_minecart");
itemNames.add("minecraft:spectral_arrow");

if (lodestoneCompassId == 0) {
throw new RuntimeException("Lodestone compass not found in item palette!");
}
Expand All @@ -248,9 +267,59 @@ public static void init() {
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<NbtMap> 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<String> 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);
}

/**
Expand Down
6 changes: 6 additions & 0 deletions connector/src/main/resources/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 2f42a4c

Please sign in to comment.