From c1e4040cb6a5e97c941c311da0b54df5dc1afbc8 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 17 Jan 2022 16:10:56 -0500 Subject: [PATCH 001/110] Don't let Bedrock players send format character See https://github.com/PaperMC/Paper/issues/7362 --- .../protocol/bedrock/BedrockTextTranslator.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockTextTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockTextTranslator.java index 035a2afe2..1a6771cc5 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockTextTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockTextTranslator.java @@ -28,6 +28,7 @@ package org.geysermc.geyser.translator.protocol.bedrock; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundChatPacket; import com.nukkitx.protocol.bedrock.packet.TextPacket; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; import org.geysermc.geyser.translator.text.MessageTranslator; @@ -44,6 +45,18 @@ public class BedrockTextTranslator extends PacketTranslator { return; } + if (message.indexOf(ChatColor.ESCAPE) != -1) { + // Filter out all escape characters - Java doesn't let you type these + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < message.length(); i++) { + char c = message.charAt(i); + if (c != ChatColor.ESCAPE) { + builder.append(c); + } + } + message = builder.toString(); + } + if (MessageTranslator.isTooLong(message, session)) { return; } From 001a1a7a155716c9745d0d65eb8daf2e45a3eea4 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 19 Jan 2022 19:30:45 -0500 Subject: [PATCH 002/110] Support proper dimensions for player dying pose --- .../geyser/entity/type/player/PlayerEntity.java | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java index 71bab079d..70b5ede99 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java @@ -382,15 +382,26 @@ public class PlayerEntity extends LivingEntity { @Override protected void setDimensions(Pose pose) { float height; + float width; switch (pose) { - case SNEAKING -> height = SNEAKING_POSE_HEIGHT; - case FALL_FLYING, SPIN_ATTACK, SWIMMING -> height = 0.6f; + case SNEAKING -> { + height = SNEAKING_POSE_HEIGHT; + width = definition.width(); + } + case FALL_FLYING, SPIN_ATTACK, SWIMMING -> { + height = 0.6f; + width = definition.width(); + } + case DYING -> { + height = 0.2f; + width = 0.2f; + } default -> { super.setDimensions(pose); return; } } - setBoundingBoxWidth(definition.width()); + setBoundingBoxWidth(width); setBoundingBoxHeight(height); } From a6004af083394e6b1c5be9ef9e7c8986ae42d8f4 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 19 Jan 2022 19:30:54 -0500 Subject: [PATCH 003/110] Minor cleanups --- .../BedrockBlockEntityDataTranslator.java | 12 ++++++---- .../entity/JavaEntityEventTranslator.java | 4 ++-- .../org/geysermc/geyser/util/FileUtils.java | 24 +++---------------- 3 files changed, 12 insertions(+), 28 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockBlockEntityDataTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockBlockEntityDataTranslator.java index 93ce71a3d..d00914fb1 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockBlockEntityDataTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockBlockEntityDataTranslator.java @@ -41,12 +41,14 @@ public class BedrockBlockEntityDataTranslator extends PacketTranslator SignUtils.JAVA_CHARACTER_WIDTH_MAX) { @@ -111,7 +113,7 @@ public class BedrockBlockEntityDataTranslator extends PacketTranslator Date: Wed, 19 Jan 2022 19:44:46 -0500 Subject: [PATCH 004/110] Better handling of invalid display tags --- .../translator/inventory/item/ItemTranslator.java | 11 ++++++----- .../inventory/item/nbt/BasicItemTranslator.java | 6 ++---- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/ItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/ItemTranslator.java index 6a2182279..5014969e1 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/ItemTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/ItemTranslator.java @@ -469,9 +469,8 @@ public abstract class ItemTranslator { public static CompoundTag translateDisplayProperties(GeyserSession session, CompoundTag tag, ItemMapping mapping, char translationColor) { boolean hasCustomName = false; if (tag != null) { - CompoundTag display = tag.get("display"); - if (display != null && display.contains("Name")) { - String name = ((StringTag) display.get("Name")).getValue(); + if (tag.get("display") instanceof CompoundTag display && display.get("Name") instanceof StringTag tagName) { + String name = tagName.getValue(); // Get the translated name and prefix it with a reset char name = MessageTranslator.convertMessageLenient(name, session.getLocale()); @@ -491,8 +490,10 @@ public abstract class ItemTranslator { if (tag == null) { tag = new CompoundTag(""); } - CompoundTag display = tag.get("display"); - if (display == null) { + CompoundTag display; + if (tag.get("display") instanceof CompoundTag oldDisplay) { + display = oldDisplay; + } else { display = new CompoundTag("display"); // Add to the new root tag tag.put(display); diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/BasicItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/BasicItemTranslator.java index 42cfc0439..a507d02cc 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/BasicItemTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/BasicItemTranslator.java @@ -51,13 +51,11 @@ public class BasicItemTranslator extends NbtItemStackTranslator { } } - CompoundTag displayTag = itemTag.get("display"); - if (displayTag == null) { + if (!(itemTag.get("display") instanceof CompoundTag displayTag)) { return; } - Tag loreTag = displayTag.get("Lore"); - if (loreTag instanceof ListTag listTag) { + if (displayTag.get("Lore") instanceof ListTag listTag) { List lore = new ArrayList<>(); for (Tag tag : listTag.getValue()) { if (!(tag instanceof StringTag)) continue; From e92633d65771bac061cadc22c95e114122af87e4 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 20 Jan 2022 18:09:35 -0500 Subject: [PATCH 005/110] Add an option to always quick-change armor With thanks to https://github.com/juancarloscp52/BedrockIfy/blob/f068217cb762a0dddd403a863d21d0686c01910d/src/main/java/me/juancarloscp52/bedrockify/client/features/quickArmorSwap/ArmorReplacer.java for making me realize this was possible. Currently disabled by default in the event that a server implementation also has this feature. May be enabled by default in the future. --- .../configuration/GeyserConfiguration.java | 2 + .../GeyserJacksonConfiguration.java | 3 + .../inventory/InventoryTranslator.java | 13 +--- ...BedrockInventoryTransactionTranslator.java | 59 ++++++++++++++----- .../geysermc/geyser/util/InventoryUtils.java | 18 ++++++ core/src/main/resources/config.yml | 4 ++ 6 files changed, 72 insertions(+), 27 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java b/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java index 06d6bdbc5..3b7cad44c 100644 --- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java @@ -78,6 +78,8 @@ public interface GeyserConfiguration { boolean isDisableBedrockScaffolding(); + boolean isAlwaysQuickChangeArmor(); + EmoteOffhandWorkaroundOption getEmoteOffhandWorkaround(); String getDefaultLocale(); diff --git a/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java b/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java index 825edf43e..97c5bfea8 100644 --- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java @@ -108,6 +108,9 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration @JsonProperty("disable-bedrock-scaffolding") private boolean disableBedrockScaffolding = false; + @JsonProperty("always-quick-change-armor") + private boolean alwaysQuickChangeArmor = false; + @JsonDeserialize(using = EmoteOffhandWorkaroundOption.Deserializer.class) @JsonProperty("emote-offhand-workaround") private EmoteOffhandWorkaroundOption emoteOffhandWorkaround = EmoteOffhandWorkaroundOption.DISABLED; diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/InventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/InventoryTranslator.java index 04d5fa3ad..e0b90db02 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/InventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/InventoryTranslator.java @@ -313,18 +313,7 @@ public abstract class InventoryTranslator { if (!isSourceCursor && destination.getContainer() == ContainerSlotType.HOTBAR || destination.getContainer() == ContainerSlotType.HOTBAR_AND_INVENTORY) { // Tell the server we're pressing one of the hotbar keys to save clicks - Click click = switch (destination.getSlot()) { - case 0 -> Click.SWAP_TO_HOTBAR_1; - case 1 -> Click.SWAP_TO_HOTBAR_2; - case 2 -> Click.SWAP_TO_HOTBAR_3; - case 3 -> Click.SWAP_TO_HOTBAR_4; - case 4 -> Click.SWAP_TO_HOTBAR_5; - case 5 -> Click.SWAP_TO_HOTBAR_6; - case 6 -> Click.SWAP_TO_HOTBAR_7; - case 7 -> Click.SWAP_TO_HOTBAR_8; - case 8 -> Click.SWAP_TO_HOTBAR_9; - default -> null; - }; + Click click = InventoryUtils.getClickForHotbarSwap(destination.getSlot()); if (click != null) { plan.add(click, sourceSlot); break; diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java index 29308f9ec..be10452f4 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java @@ -31,6 +31,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; import com.github.steveice10.mc.protocol.data.game.entity.player.InteractAction; import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerAction; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundContainerClickPacket; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundInteractPacket; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundPlayerActionPacket; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundUseItemOnPacket; @@ -40,21 +41,26 @@ import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.LevelEventType; import com.nukkitx.protocol.bedrock.data.inventory.*; import com.nukkitx.protocol.bedrock.packet.*; +import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.type.CommandBlockMinecartEntity; import org.geysermc.geyser.entity.type.Entity; -import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.type.ItemFrameEntity; import org.geysermc.geyser.inventory.GeyserItemStack; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.translator.protocol.PacketTranslator; -import org.geysermc.geyser.translator.protocol.Translator; -import org.geysermc.geyser.translator.sound.EntitySoundInteractionTranslator; +import org.geysermc.geyser.inventory.Inventory; +import org.geysermc.geyser.inventory.click.Click; import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMappings; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.translator.sound.EntitySoundInteractionTranslator; import org.geysermc.geyser.util.BlockUtils; +import org.geysermc.geyser.util.InventoryUtils; +import java.util.Collections; +import java.util.List; import java.util.concurrent.TimeUnit; /** @@ -269,16 +275,6 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator { - if (packet.getActions().size() == 1 && packet.getLegacySlots().size() > 0) { - InventoryActionData actionData = packet.getActions().get(0); - LegacySetItemSlotData slotData = packet.getLegacySlots().get(0); - if (slotData.getContainerId() == 6 && actionData.getToItem().getId() != 0) { - // The player is trying to swap out an armor piece that already has an item in it - // Java Edition does not allow this; let's revert it - session.getInventoryTranslator().updateInventory(session, session.getPlayerInventory()); - } - } - // Handled when sneaking if (session.getPlayerInventory().getItemInHand().getJavaId() == mappings.getStoredItems().shield().getJavaId()) { break; @@ -298,6 +294,39 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator legacySlots = packet.getLegacySlots(); + if (packet.getActions().size() == 1 && legacySlots.size() > 0) { + InventoryActionData actionData = packet.getActions().get(0); + LegacySetItemSlotData slotData = legacySlots.get(0); + if (slotData.getContainerId() == 6 && actionData.getToItem().getId() != 0) { + // The player is trying to swap out an armor piece that already has an item in it + if (session.getGeyser().getConfig().isAlwaysQuickChangeArmor()) { + // Java doesn't know when a player is in its own inventory and not, so we + // can abuse this feature to send a swap inventory packet + int bedrockHotbarSlot = packet.getHotbarSlot(); + Click click = InventoryUtils.getClickForHotbarSwap(bedrockHotbarSlot); + if (click != null && slotData.getSlots().length != 0) { + Inventory playerInventory = session.getPlayerInventory(); + // Bedrock sends us the index of the slot in the armor container; armor in Java + // Edition is offset by 5 in the player inventory + int armorSlot = slotData.getSlots()[0] + 5; + GeyserItemStack armorSlotItem = playerInventory.getItem(armorSlot); + GeyserItemStack hotbarItem = playerInventory.getItem(playerInventory.getOffsetForHotbar(bedrockHotbarSlot)); + playerInventory.setItem(armorSlot, hotbarItem, session); + playerInventory.setItem(bedrockHotbarSlot, armorSlotItem, session); + + ServerboundContainerClickPacket clickPacket = new ServerboundContainerClickPacket( + playerInventory.getId(), playerInventory.getStateId(), armorSlot, + click.actionType, click.action, null, Collections.emptyMap()); + session.sendDownstreamPacket(clickPacket); + } + } else { + // Disallowed; let's revert + session.getInventoryTranslator().updateInventory(session, session.getPlayerInventory()); + } + } + } } case 2 -> { int blockState = session.getGameMode() == GameMode.CREATIVE ? diff --git a/core/src/main/java/org/geysermc/geyser/util/InventoryUtils.java b/core/src/main/java/org/geysermc/geyser/util/InventoryUtils.java index 76530f396..72f20797e 100644 --- a/core/src/main/java/org/geysermc/geyser/util/InventoryUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/InventoryUtils.java @@ -41,6 +41,7 @@ import org.geysermc.geyser.inventory.Container; import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.inventory.Inventory; import org.geysermc.geyser.inventory.PlayerInventory; +import org.geysermc.geyser.inventory.click.Click; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.GeyserLocale; @@ -50,6 +51,7 @@ import org.geysermc.geyser.translator.inventory.chest.DoubleChestInventoryTransl import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.type.ItemMapping; +import javax.annotation.Nullable; import java.util.Collections; import java.util.Objects; import java.util.concurrent.TimeUnit; @@ -330,4 +332,20 @@ public class InventoryUtils { session.sendUpstreamPacket(hotbarPacket); // No need to send a Java packet as Bedrock sends a confirmation packet back that we translate } + + @Nullable + public static Click getClickForHotbarSwap(int slot) { + return switch (slot) { + case 0 -> Click.SWAP_TO_HOTBAR_1; + case 1 -> Click.SWAP_TO_HOTBAR_2; + case 2 -> Click.SWAP_TO_HOTBAR_3; + case 3 -> Click.SWAP_TO_HOTBAR_4; + case 4 -> Click.SWAP_TO_HOTBAR_5; + case 5 -> Click.SWAP_TO_HOTBAR_6; + case 6 -> Click.SWAP_TO_HOTBAR_7; + case 7 -> Click.SWAP_TO_HOTBAR_8; + case 8 -> Click.SWAP_TO_HOTBAR_9; + default -> null; + }; + } } diff --git a/core/src/main/resources/config.yml b/core/src/main/resources/config.yml index d762220a5..00e2521f3 100644 --- a/core/src/main/resources/config.yml +++ b/core/src/main/resources/config.yml @@ -128,6 +128,10 @@ show-coordinates: true # Whether Bedrock players are blocked from performing their scaffolding-style bridging. disable-bedrock-scaffolding: false +# Whether Bedrock players can right-click outside of their inventory to replace armor in their inventory, even if the +# armor slot is already occupied (which Java Edition doesn't allow) +always-quick-change-armor: false + # If set, when a Bedrock player performs any emote, it will swap the offhand and mainhand items, just like the Java Edition keybind # There are three options this can be set to: # disabled - the default/fallback, which doesn't apply this workaround From 1fba96c339ebbe1775bc886679afb5f35815c61f Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 22 Jan 2022 16:22:27 -0500 Subject: [PATCH 006/110] Address armor stand invisibility edge case See https://github.com/GeyserMC/Geyser/issues/2780 --- .../java/org/geysermc/geyser/entity/GeyserDirtyMetadata.java | 2 +- .../geysermc/geyser/entity/type/living/ArmorStandEntity.java | 2 +- .../protocol/java/entity/JavaSetEntityDataTranslator.java | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/entity/GeyserDirtyMetadata.java b/core/src/main/java/org/geysermc/geyser/entity/GeyserDirtyMetadata.java index caa373549..f0095d26a 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/GeyserDirtyMetadata.java +++ b/core/src/main/java/org/geysermc/geyser/entity/GeyserDirtyMetadata.java @@ -34,7 +34,7 @@ import java.util.Map; /** * A write-only wrapper for temporarily storing entity metadata that will be sent to Bedrock. */ -public class GeyserDirtyMetadata { +public final class GeyserDirtyMetadata { private final Map metadata = new Object2ObjectLinkedOpenHashMap<>(); public void put(EntityData entityData, Object value) { diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/ArmorStandEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/ArmorStandEntity.java index 9980cd2c1..10086be9c 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/ArmorStandEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/ArmorStandEntity.java @@ -136,7 +136,7 @@ public class ArmorStandEntity extends LivingEntity { } isSmall = newIsSmall; - if (!isMarker) { + if (!isMarker && !isInvisible) { // Addition for isInvisible check caused by https://github.com/GeyserMC/Geyser/issues/2780 toggleSmallStatus(); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetEntityDataTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetEntityDataTranslator.java index 235ecb1cd..ed9129c26 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetEntityDataTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetEntityDataTranslator.java @@ -27,12 +27,12 @@ package org.geysermc.geyser.translator.protocol.java.entity; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.ClientboundSetEntityDataPacket; -import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.entity.InteractiveTagManager; +import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; -import org.geysermc.geyser.entity.InteractiveTagManager; @Translator(packet = ClientboundSetEntityDataPacket.class) public class JavaSetEntityDataTranslator extends PacketTranslator { From f682cf1326775734f3c9289c8c488e4cd0c82a55 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 22 Jan 2022 16:36:41 -0500 Subject: [PATCH 007/110] Yes, Geyser supports Bedrock 1.18.0/1/2. --- .../java/org/geysermc/geyser/network/MinecraftProtocol.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java b/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java index d28b95203..f605f9089 100644 --- a/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java +++ b/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java @@ -60,7 +60,7 @@ public final class MinecraftProtocol { static { SUPPORTED_BEDROCK_CODECS.add(Bedrock_v465.V465_CODEC); SUPPORTED_BEDROCK_CODECS.add(Bedrock_v471.V471_CODEC); - SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC); + SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC.toBuilder().minecraftVersion("1.18.0/1.18.1/1.18.2").build()); } /** From 5ce2c113ae100a09e6a743297bf5eab5b16ae3dc Mon Sep 17 00:00:00 2001 From: Tim203 Date: Fri, 28 Jan 2022 13:08:10 +0100 Subject: [PATCH 008/110] Remove IPv6 scope if present --- .../java/org/geysermc/geyser/session/GeyserSession.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index 742a2e4a9..99c8c5cc4 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -796,6 +796,13 @@ public class GeyserSession implements GeyserConnection, CommandSender { FloodgateSkinUploader skinUploader = geyser.getSkinUploader(); FloodgateCipher cipher = geyser.getCipher(); + String bedrockAddress = upstream.getAddress().getAddress().getHostAddress(); + // both BungeeCord and Velocity remove the IPv6 scope (if there is one) for Spigot + int ipv6ScopeIndex = bedrockAddress.indexOf('%'); + if (ipv6ScopeIndex != -1) { + bedrockAddress = bedrockAddress.substring(0, ipv6ScopeIndex); + } + encryptedData = cipher.encryptFromString(BedrockData.of( clientData.getGameVersion(), authData.name(), @@ -804,7 +811,7 @@ public class GeyserSession implements GeyserConnection, CommandSender { clientData.getLanguageCode(), clientData.getUiProfile().ordinal(), clientData.getCurrentInputMode().ordinal(), - upstream.getAddress().getAddress().getHostAddress(), + bedrockAddress, skinUploader.getId(), skinUploader.getVerifyCode() ).toString()); From 14882534c0f14be29c05c0cafb593889a2e0a550 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 30 Jan 2022 11:05:29 -0500 Subject: [PATCH 009/110] Don't fully translate item data to compare net IDs Just compare the item mappings of the two Java items. This should shave some NBT and display conversion processing time down. --- .../geysermc/geyser/inventory/Inventory.java | 6 +++- .../geyser/session/cache/LodestoneCache.java | 28 +++++++--------- .../inventory/item/BannerTranslator.java | 6 ++-- .../inventory/item/CompassTranslator.java | 33 +++++++++++++------ .../inventory/item/ItemTranslator.java | 31 ++++++++++++----- .../inventory/item/PotionTranslator.java | 2 +- .../inventory/item/TippedArrowTranslator.java | 2 +- ...tionTrackingDBClientRequestTranslator.java | 4 +-- 8 files changed, 68 insertions(+), 44 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/inventory/Inventory.java b/core/src/main/java/org/geysermc/geyser/inventory/Inventory.java index 3b307ba8d..26dc261a0 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/Inventory.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/Inventory.java @@ -35,7 +35,9 @@ import lombok.NonNull; import lombok.Setter; import lombok.ToString; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.inventory.item.ItemTranslator; import org.jetbrains.annotations.Range; import java.util.Arrays; @@ -136,7 +138,9 @@ public abstract class Inventory { protected void updateItemNetId(GeyserItemStack oldItem, GeyserItemStack newItem, GeyserSession session) { if (!newItem.isEmpty()) { - if (newItem.getItemData(session).equals(oldItem.getItemData(session), false, false, false)) { + ItemMapping oldMapping = ItemTranslator.getBedrockItemMapping(session, oldItem); + ItemMapping newMapping = ItemTranslator.getBedrockItemMapping(session, newItem); + if (oldMapping.getBedrockId() == newMapping.getBedrockId()) { newItem.setNetId(oldItem.getNetId()); } else { newItem.setNetId(session.getNextItemNetId()); diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/LodestoneCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/LodestoneCache.java index f0cbbb189..05c2628df 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/LodestoneCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/LodestoneCache.java @@ -30,9 +30,6 @@ import com.github.steveice10.opennbt.tag.builtin.IntTag; import com.github.steveice10.opennbt.tag.builtin.StringTag; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import lombok.AllArgsConstructor; -import lombok.EqualsAndHashCode; -import lombok.Getter; import org.geysermc.geyser.inventory.GeyserItemStack; import javax.annotation.Nullable; @@ -43,7 +40,7 @@ import java.util.WeakHashMap; * A temporary cache for lodestone information. * Bedrock requests the lodestone position information separately from the item. */ -public class LodestoneCache { +public final class LodestoneCache { /** * A list of any GeyserItemStacks that are lodestones. Used mainly to minimize Bedrock's "pop-in" effect * when a new item has been created; instead we can re-use already existing IDs @@ -121,8 +118,16 @@ public class LodestoneCache { } public @Nullable LodestonePos getPos(int id) { - // We should not need to check the activeLodestones map as Bedrock should already be aware of this ID - return this.lodestones.remove(id); + LodestonePos pos = this.lodestones.remove(id); + if (pos != null) { + return pos; + } + for (LodestonePos activePos : this.activeLodestones.values()) { + if (activePos.id == id) { + return activePos; + } + } + return null; } public void clear() { @@ -131,16 +136,7 @@ public class LodestoneCache { this.lodestones.clear(); } - @Getter - @AllArgsConstructor - @EqualsAndHashCode - public static class LodestonePos { - private final int id; - private final int x; - private final int y; - private final int z; - private final String dimension; - + public record LodestonePos(int id, int x, int y, int z, String dimension) { boolean equals(int x, int y, int z, String dimension) { return this.x == x && this.y == y && this.z == z && this.dimension.equals(dimension); } diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/BannerTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/BannerTranslator.java index 3c566e76c..a5c3235a2 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/BannerTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/BannerTranslator.java @@ -155,7 +155,7 @@ public class BannerTranslator extends ItemTranslator { } @Override - public ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping mapping, ItemMappings mappings) { + protected ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping mapping, ItemMappings mappings) { if (itemStack.getNbt() == null) { return super.translateToBedrock(itemStack, mapping, mappings); } @@ -163,9 +163,7 @@ public class BannerTranslator extends ItemTranslator { ItemData.Builder builder = super.translateToBedrock(itemStack, mapping, mappings); CompoundTag blockEntityTag = itemStack.getNbt().get("BlockEntityTag"); - if (blockEntityTag != null && blockEntityTag.contains("Patterns")) { - ListTag patterns = blockEntityTag.get("Patterns"); - + if (blockEntityTag != null && blockEntityTag.get("Patterns") instanceof ListTag patterns) { NbtMapBuilder nbtBuilder = builder.build().getTag().toBuilder(); //TODO fix ugly hack if (patterns.equals(OMINOUS_BANNER_PATTERN)) { // Remove the current patterns and set the ominous banner type diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/CompassTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/CompassTranslator.java index 65f26542f..9637f1aa9 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/CompassTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/CompassTranslator.java @@ -26,7 +26,9 @@ package org.geysermc.geyser.translator.inventory.item; import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; -import com.github.steveice10.opennbt.tag.builtin.*; +import com.github.steveice10.opennbt.tag.builtin.ByteTag; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import org.geysermc.geyser.network.MinecraftProtocol; import org.geysermc.geyser.registry.Registries; @@ -51,19 +53,30 @@ public class CompassTranslator extends ItemTranslator { } @Override - public ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping mapping, ItemMappings mappings) { - if (itemStack.getNbt() == null) return super.translateToBedrock(itemStack, mapping, mappings); - - Tag lodestoneTag = itemStack.getNbt().get("LodestoneTracked"); - if (lodestoneTag instanceof ByteTag) { - // Get the fake lodestonecompass entry - mapping = mappings.getStoredItems().lodestoneCompass(); - // NBT will be translated in nbt/LodestoneCompassTranslator + protected ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping mapping, ItemMappings mappings) { + if (isLodestoneCompass(itemStack.getNbt())) { + // NBT will be translated in nbt/LodestoneCompassTranslator if applicable + return super.translateToBedrock(itemStack, mappings.getStoredItems().lodestoneCompass(), mappings); } - return super.translateToBedrock(itemStack, mapping, mappings); } + @Override + protected ItemMapping getItemMapping(int javaId, CompoundTag nbt, ItemMappings mappings) { + if (isLodestoneCompass(nbt)) { + return mappings.getStoredItems().lodestoneCompass(); + } + return super.getItemMapping(javaId, nbt, mappings); + } + + private boolean isLodestoneCompass(CompoundTag nbt) { + if (nbt != null) { + Tag lodestoneTag = nbt.get("LodestoneTracked"); + return lodestoneTag instanceof ByteTag; + } + return false; + } + @Override public ItemStack translateToJava(ItemData itemData, ItemMapping mapping, ItemMappings mappings) { if (mapping.getBedrockIdentifier().equals("minecraft:lodestone_compass")) { diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/ItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/ItemTranslator.java index 5014969e1..b8a7b60e2 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/ItemTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/ItemTranslator.java @@ -37,6 +37,7 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMappings; @@ -157,18 +158,13 @@ public abstract class ItemTranslator { nbt = translateDisplayProperties(session, nbt, bedrockItem); if (session.isAdvancedTooltips()) { - nbt = addAdvancedTooltips(nbt, session.getItemMappings().getMapping(stack), session.getLocale()); + nbt = addAdvancedTooltips(nbt, bedrockItem, session.getLocale()); } ItemStack itemStack = new ItemStack(stack.getId(), stack.getAmount(), nbt); - ItemData.Builder builder; - ItemTranslator itemStackTranslator = ITEM_STACK_TRANSLATORS.get(bedrockItem.getJavaId()); - if (itemStackTranslator != null) { - builder = itemStackTranslator.translateToBedrock(itemStack, bedrockItem, session.getItemMappings()); - } else { - builder = DEFAULT_TRANSLATOR.translateToBedrock(itemStack, bedrockItem, session.getItemMappings()); - } + ItemTranslator itemStackTranslator = ITEM_STACK_TRANSLATORS.getOrDefault(bedrockItem.getJavaId(), DEFAULT_TRANSLATOR); + ItemData.Builder builder = itemStackTranslator.translateToBedrock(itemStack, bedrockItem, session.getItemMappings()); if (bedrockItem.isBlock()) { builder.blockRuntimeId(bedrockItem.getBedrockBlockId()); } @@ -263,6 +259,19 @@ public abstract class ItemTranslator { return canModifyBedrock; } + /** + * Given an item stack, determine the item mapping that should be applied to Bedrock players. + */ + @Nonnull + public static ItemMapping getBedrockItemMapping(GeyserSession session, @Nonnull GeyserItemStack itemStack) { + if (itemStack.isEmpty()) { + return ItemMapping.AIR; + } + int javaId = itemStack.getJavaId(); + return ITEM_STACK_TRANSLATORS.getOrDefault(javaId, DEFAULT_TRANSLATOR) + .getItemMapping(javaId, itemStack.getNbt(), session.getItemMappings()); + } + private static final ItemTranslator DEFAULT_TRANSLATOR = new ItemTranslator() { @Override public List getAppliedItems() { @@ -270,7 +279,7 @@ public abstract class ItemTranslator { } }; - public ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping mapping, ItemMappings mappings) { + protected ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping mapping, ItemMappings mappings) { if (itemStack == null) { // Return, essentially, air return ItemData.builder(); @@ -295,6 +304,10 @@ public abstract class ItemTranslator { public abstract List getAppliedItems(); + protected ItemMapping getItemMapping(int javaId, CompoundTag nbt, ItemMappings mappings) { + return mappings.getMapping(javaId); + } + public NbtMap translateNbtToBedrock(CompoundTag tag) { NbtMapBuilder builder = NbtMap.builder(); if (tag.getValue() != null && !tag.getValue().isEmpty()) { diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/PotionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/PotionTranslator.java index 272092da6..54a6deadb 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/PotionTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/PotionTranslator.java @@ -54,7 +54,7 @@ public class PotionTranslator extends ItemTranslator { } @Override - public ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping mapping, ItemMappings mappings) { + protected ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping mapping, ItemMappings mappings) { if (itemStack.getNbt() == null) return super.translateToBedrock(itemStack, mapping, mappings); Tag potionTag = itemStack.getNbt().get("Potion"); if (potionTag instanceof StringTag) { diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/TippedArrowTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/TippedArrowTranslator.java index 4925d3e69..35e8baa07 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/TippedArrowTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/TippedArrowTranslator.java @@ -59,7 +59,7 @@ public class TippedArrowTranslator extends ItemTranslator { } @Override - public ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping mapping, ItemMappings mappings) { + protected ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping mapping, ItemMappings mappings) { if (!mapping.getJavaIdentifier().equals("minecraft:tipped_arrow") || itemStack.getNbt() == null) { // We're only concerned about minecraft:arrow when translating Bedrock -> Java return super.translateToBedrock(itemStack, mapping, mappings); diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockPositionTrackingDBClientRequestTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockPositionTrackingDBClientRequestTranslator.java index a6551afbd..f3d0ff344 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockPositionTrackingDBClientRequestTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockPositionTrackingDBClientRequestTranslator.java @@ -58,14 +58,14 @@ public class BedrockPositionTrackingDBClientRequestTranslator extends PacketTran // Build the NBT data for the update NbtMapBuilder builder = NbtMap.builder(); - builder.putInt("dim", DimensionUtils.javaToBedrock(pos.getDimension())); + builder.putInt("dim", DimensionUtils.javaToBedrock(pos.dimension())); builder.putString("id", "0x" + String.format("%08X", packet.getTrackingId())); builder.putByte("version", (byte) 1); // Not sure what this is for builder.putByte("status", (byte) 0); // Not sure what this is for // Build the position for the update - builder.putList("pos", NbtType.INT, pos.getX(), pos.getY(), pos.getZ()); + builder.putList("pos", NbtType.INT, pos.x(), pos.y(), pos.z()); broadcastPacket.setTag(builder.build()); session.sendUpstreamPacket(broadcastPacket); From d0fa2d2b055e74fa28b0eaed63c8f61a4e6f32c6 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 30 Jan 2022 11:14:51 -0500 Subject: [PATCH 010/110] Don't send respawn code until Java is ready Fixes #2668 --- .../bedrock/BedrockRespawnTranslator.java | 25 ------------------- .../protocol/java/JavaRespawnTranslator.java | 2 ++ .../player/JavaPlayerPositionTranslator.java | 9 ++++--- 3 files changed, 7 insertions(+), 29 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockRespawnTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockRespawnTranslator.java index 77b7143b2..1631ea4c7 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockRespawnTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockRespawnTranslator.java @@ -27,10 +27,7 @@ package org.geysermc.geyser.translator.protocol.bedrock; import com.github.steveice10.mc.protocol.data.game.ClientCommand; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundClientCommandPacket; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket; import com.nukkitx.protocol.bedrock.packet.RespawnPacket; -import org.geysermc.geyser.entity.type.player.PlayerEntity; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; @@ -41,28 +38,6 @@ public class BedrockRespawnTranslator extends PacketTranslator { @Override public void translate(GeyserSession session, RespawnPacket packet) { if (packet.getState() == RespawnPacket.State.CLIENT_READY) { - // Previously we only sent the respawn packet before the server finished loading - // The message included was 'Otherwise when immediate respawn is on the client never loads' - // But I assume the new if statement below fixes that problem - RespawnPacket respawnPacket = new RespawnPacket(); - respawnPacket.setRuntimeEntityId(0); - respawnPacket.setPosition(Vector3f.ZERO); - respawnPacket.setState(RespawnPacket.State.SERVER_READY); - session.sendUpstreamPacket(respawnPacket); - - if (session.isSpawned()) { - // Client might be stuck; resend spawn information - PlayerEntity entity = session.getPlayerEntity(); - entity.updateBedrockMetadata(); // TODO test? - - MovePlayerPacket movePlayerPacket = new MovePlayerPacket(); - movePlayerPacket.setRuntimeEntityId(entity.getGeyserId()); - movePlayerPacket.setPosition(entity.getPosition()); - movePlayerPacket.setRotation(entity.getBedrockRotation()); - movePlayerPacket.setMode(MovePlayerPacket.Mode.RESPAWN); - session.sendUpstreamPacket(movePlayerPacket); - } - ServerboundClientCommandPacket javaRespawnPacket = new ServerboundClientCommandPacket(ClientCommand.RESPAWN); session.sendDownstreamPacket(javaRespawnPacket); } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaRespawnTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaRespawnTranslator.java index 7b198b575..03d006a50 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaRespawnTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaRespawnTranslator.java @@ -46,6 +46,8 @@ public class JavaRespawnTranslator extends PacketTranslator Date: Sun, 30 Jan 2022 11:15:07 -0500 Subject: [PATCH 011/110] Several inventory and parity improvements These changes fix up things that were missed with Java Edition inventory changes in 1.17 and 1.17.1. Working with the inventory in modern versions should be much nicer. --- .../org/geysermc/geyser/GeyserLogger.java | 11 ++ .../geysermc/geyser/inventory/Container.java | 5 +- .../geysermc/geyser/inventory/Inventory.java | 8 +- .../geyser/inventory/click/ClickPlan.java | 178 +++++++++--------- .../geyser/session/GeyserSession.java | 9 + .../inventory/BeaconInventoryTranslator.java | 9 +- .../CraftingInventoryTranslator.java | 5 + .../EnchantingInventoryTranslator.java | 19 +- .../inventory/InventoryTranslator.java | 72 +++---- .../inventory/PlayerInventoryTranslator.java | 18 +- ...BedrockInventoryTransactionTranslator.java | 10 +- .../protocol/java/JavaRecipeTranslator.java | 8 +- .../JavaContainerSetContentTranslator.java | 26 ++- .../JavaContainerSetSlotTranslator.java | 134 +++---------- .../geysermc/geyser/util/InventoryUtils.java | 117 ++++++++++++ 15 files changed, 364 insertions(+), 265 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/GeyserLogger.java b/core/src/main/java/org/geysermc/geyser/GeyserLogger.java index a61c5db25..b47801cb5 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserLogger.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserLogger.java @@ -25,6 +25,8 @@ package org.geysermc.geyser; +import javax.annotation.Nullable; + public interface GeyserLogger { /** @@ -78,6 +80,15 @@ public interface GeyserLogger { */ void debug(String message); + /** + * Logs an object to console if debug mode is enabled + * + * @param object the object to log + */ + default void debug(@Nullable Object object) { + debug(String.valueOf(object)); + } + /** * Sets if the logger should print debug messages * diff --git a/core/src/main/java/org/geysermc/geyser/inventory/Container.java b/core/src/main/java/org/geysermc/geyser/inventory/Container.java index 073887a64..569802a5a 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/Container.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/Container.java @@ -27,11 +27,12 @@ package org.geysermc.geyser.inventory; import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType; import lombok.Getter; -import lombok.NonNull; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.inventory.InventoryTranslator; import org.jetbrains.annotations.Range; +import javax.annotation.Nonnull; + /** * Combination of {@link Inventory} and {@link PlayerInventory} */ @@ -66,7 +67,7 @@ public class Container extends Inventory { } @Override - public void setItem(int slot, @NonNull GeyserItemStack newItem, GeyserSession session) { + public void setItem(int slot, @Nonnull GeyserItemStack newItem, GeyserSession session) { if (slot < this.size) { super.setItem(slot, newItem, session); } else { diff --git a/core/src/main/java/org/geysermc/geyser/inventory/Inventory.java b/core/src/main/java/org/geysermc/geyser/inventory/Inventory.java index 26dc261a0..ca7e90a25 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/Inventory.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/Inventory.java @@ -31,7 +31,6 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.Tag; import com.nukkitx.math.vector.Vector3i; import lombok.Getter; -import lombok.NonNull; import lombok.Setter; import lombok.ToString; import org.geysermc.geyser.GeyserImpl; @@ -40,11 +39,11 @@ import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.inventory.item.ItemTranslator; import org.jetbrains.annotations.Range; +import javax.annotation.Nonnull; import java.util.Arrays; @ToString public abstract class Inventory { - @Getter protected final int id; @@ -72,8 +71,7 @@ public abstract class Inventory { protected final ContainerType containerType; @Getter - @Setter - protected String title; + protected final String title; protected final GeyserItemStack[] items; @@ -115,7 +113,7 @@ public abstract class Inventory { public abstract int getOffsetForHotbar(@Range(from = 0, to = 8) int slot); - public void setItem(int slot, @NonNull GeyserItemStack newItem, GeyserSession session) { + public void setItem(int slot, @Nonnull GeyserItemStack newItem, GeyserSession session) { if (slot > this.size) { session.getGeyser().getLogger().debug("Tried to set an item out of bounds! " + this); return; diff --git a/core/src/main/java/org/geysermc/geyser/inventory/click/ClickPlan.java b/core/src/main/java/org/geysermc/geyser/inventory/click/ClickPlan.java index e973beadc..e6eeea689 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/click/ClickPlan.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/click/ClickPlan.java @@ -28,7 +28,6 @@ package org.geysermc.geyser.inventory.click; import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.github.steveice10.mc.protocol.data.game.inventory.ContainerActionType; import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType; -import com.github.steveice10.mc.protocol.data.game.inventory.MoveToHotbarAction; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundContainerClickPacket; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; @@ -40,20 +39,22 @@ import org.geysermc.geyser.inventory.SlotType; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.inventory.CraftingInventoryTranslator; import org.geysermc.geyser.translator.inventory.InventoryTranslator; -import org.geysermc.geyser.translator.inventory.PlayerInventoryTranslator; import org.geysermc.geyser.util.InventoryUtils; import org.jetbrains.annotations.Contract; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.ListIterator; -public class ClickPlan { +public final class ClickPlan { private final List plan = new ArrayList<>(); private final Int2ObjectMap simulatedItems; + /** + * Used for 1.17.1+ proper packet translation - any non-cursor item that is changed in a single transaction gets sent here. + */ + private Int2ObjectMap changedItems; private GeyserItemStack simulatedCursor; - private boolean simulating; + private boolean finished; private final GeyserSession session; private final InventoryTranslator translator; @@ -66,21 +67,11 @@ public class ClickPlan { this.inventory = inventory; this.simulatedItems = new Int2ObjectOpenHashMap<>(inventory.getSize()); + this.changedItems = null; this.simulatedCursor = session.getPlayerInventory().getCursor().copy(); - this.simulating = true; + this.finished = false; - if (translator instanceof PlayerInventoryTranslator) { - gridSize = 4; - } else if (translator instanceof CraftingInventoryTranslator) { - gridSize = 9; - } else { - gridSize = -1; - } - } - - private void resetSimulation() { - this.simulatedItems.clear(); - this.simulatedCursor = session.getPlayerInventory().getCursor().copy(); + gridSize = translator.getGridSize(); } public void add(Click click, int slot) { @@ -88,7 +79,7 @@ public class ClickPlan { } public void add(Click click, int slot, boolean force) { - if (!simulating) + if (finished) throw new UnsupportedOperationException("ClickPlan already executed"); if (click == Click.LEFT_OUTSIDE || click == Click.RIGHT_OUTSIDE) { @@ -97,12 +88,10 @@ public class ClickPlan { ClickAction action = new ClickAction(click, slot, force); plan.add(action); - simulateAction(action); } public void execute(boolean refresh) { //update geyser inventory after simulation to avoid net id desync - resetSimulation(); ListIterator planIter = plan.listIterator(); while (planIter.hasNext()) { ClickAction action = planIter.next(); @@ -112,33 +101,48 @@ public class ClickPlan { refresh = true; } - //int stateId = stateIdHack(action); + changedItems = new Int2ObjectOpenHashMap<>(); - //simulateAction(action); + boolean emulatePost1_16Logic = session.isEmulatePost1_16Logic(); + + int stateId; + if (emulatePost1_16Logic) { + stateId = stateIdHack(action); + simulateAction(action); + } else { + stateId = inventory.getStateId(); + } ItemStack clickedItemStack; if (!planIter.hasNext() && refresh) { clickedItemStack = InventoryUtils.REFRESH_ITEM; - } else if (action.click.actionType == ContainerActionType.DROP_ITEM || action.slot == Click.OUTSIDE_SLOT) { - clickedItemStack = null; } else { - //// The action must be simulated first as Java expects the new contents of the cursor (as of 1.18.1) - //clickedItemStack = simulatedCursor.getItemStack(); TODO fix - this is the proper behavior but it terribly breaks 1.16.5 - clickedItemStack = getItem(action.slot).getItemStack(); + if (emulatePost1_16Logic) { + // The action must be simulated first as Java expects the new contents of the cursor (as of 1.18.1) + clickedItemStack = simulatedCursor.getItemStack(); + } else { + if (action.click.actionType == ContainerActionType.DROP_ITEM || action.slot == Click.OUTSIDE_SLOT) { + clickedItemStack = null; + } else { + clickedItemStack = getItem(action.slot).getItemStack(); + } + } + } + + if (!emulatePost1_16Logic) { + simulateAction(action); } ServerboundContainerClickPacket clickPacket = new ServerboundContainerClickPacket( inventory.getId(), - inventory.getStateId(), + stateId, action.slot, action.click.actionType, action.click.action, clickedItemStack, - Collections.emptyMap() // Anything else we change, at this time, should have a packet sent to address + changedItems ); - simulateAction(action); - session.sendDownstreamPacket(clickPacket); } @@ -146,19 +150,11 @@ public class ClickPlan { for (Int2ObjectMap.Entry simulatedSlot : simulatedItems.int2ObjectEntrySet()) { inventory.setItem(simulatedSlot.getIntKey(), simulatedSlot.getValue(), session); } - simulating = false; + finished = true; } public GeyserItemStack getItem(int slot) { - return getItem(slot, true); - } - - public GeyserItemStack getItem(int slot, boolean generate) { - if (generate) { - return simulatedItems.computeIfAbsent(slot, k -> inventory.getItem(slot).copy()); - } else { - return simulatedItems.getOrDefault(slot, inventory.getItem(slot)); - } + return simulatedItems.computeIfAbsent(slot, k -> inventory.getItem(slot).copy()); } public GeyserItemStack getCursor() { @@ -166,23 +162,38 @@ public class ClickPlan { } private void setItem(int slot, GeyserItemStack item) { - if (simulating) { - simulatedItems.put(slot, item); - } else { - inventory.setItem(slot, item, session); - } + simulatedItems.put(slot, item); + onSlotItemChange(slot, item); } private void setCursor(GeyserItemStack item) { - if (simulating) { - simulatedCursor = item; - } else { - session.getPlayerInventory().setCursor(item, session); - } + simulatedCursor = item; + } + + private void add(int slot, GeyserItemStack itemStack, int amount) { + itemStack.add(amount); + onSlotItemChange(slot, itemStack); + } + + private void sub(int slot, GeyserItemStack itemStack, int amount) { + itemStack.sub(amount); + onSlotItemChange(slot, itemStack); + } + + private void setAmount(int slot, GeyserItemStack itemStack, int amount) { + itemStack.setAmount(amount); + onSlotItemChange(slot, itemStack); + } + + /** + * Does not need to be called for the cursor + */ + private void onSlotItemChange(int slot, GeyserItemStack itemStack) { + changedItems.put(slot, itemStack.getItemStack()); } private void simulateAction(ClickAction action) { - GeyserItemStack cursor = simulating ? getCursor() : session.getPlayerInventory().getCursor(); + GeyserItemStack cursor = getCursor(); switch (action.click) { case LEFT_OUTSIDE -> { setCursor(GeyserItemStack.EMPTY); @@ -196,7 +207,7 @@ public class ClickPlan { } } - GeyserItemStack clicked = simulating ? getItem(action.slot) : inventory.getItem(action.slot); + GeyserItemStack clicked = getItem(action.slot); if (translator.getSlotType(action.slot) == SlotType.OUTPUT) { switch (action.click) { case LEFT, RIGHT -> { @@ -206,6 +217,7 @@ public class ClickPlan { cursor.add(clicked.getAmount()); } reduceCraftingGrid(false); + setItem(action.slot, GeyserItemStack.EMPTY); // Matches Java Edition 1.18.1 } case LEFT_SHIFT -> reduceCraftingGrid(true); } @@ -217,20 +229,20 @@ public class ClickPlan { setItem(action.slot, cursor); } else { setCursor(GeyserItemStack.EMPTY); - clicked.add(cursor.getAmount()); + add(action.slot, clicked, cursor.getAmount()); } break; case RIGHT: if (cursor.isEmpty() && !clicked.isEmpty()) { int half = clicked.getAmount() / 2; //smaller half setCursor(clicked.copy(clicked.getAmount() - half)); //larger half - clicked.setAmount(half); + setAmount(action.slot, clicked, half); } else if (!cursor.isEmpty() && clicked.isEmpty()) { cursor.sub(1); setItem(action.slot, cursor.copy(1)); } else if (InventoryUtils.canStack(cursor, clicked)) { cursor.sub(1); - clicked.add(1); + add(action.slot, clicked, 1); } break; case SWAP_TO_HOTBAR_1: @@ -265,7 +277,7 @@ public class ClickPlan { break; case DROP_ONE: if (!clicked.isEmpty()) { - clicked.sub(1); + sub(action.slot, clicked, 1); } break; case DROP_ALL: @@ -279,7 +291,7 @@ public class ClickPlan { * Swap between two inventory slots without a cursor. This should only be used with {@link ContainerActionType#MOVE_TO_HOTBAR_SLOT} */ private void swap(int sourceSlot, int destSlot, GeyserItemStack sourceItem) { - GeyserItemStack destinationItem = simulating ? getItem(destSlot) : inventory.getItem(destSlot); + GeyserItemStack destinationItem = getItem(destSlot); setItem(sourceSlot, destinationItem); setItem(destSlot, sourceItem); } @@ -292,63 +304,44 @@ public class ClickPlan { stateId = inventory.getStateId(); } - // This is a hack. - // Java will never ever send more than one container click packet per set of actions. + // Java will never ever send more than one container click packet per set of actions*. + // *(exception being Java's "quick craft"/painting feature) // Bedrock might, and this would generally fall into one of two categories: // - Bedrock is sending an item directly from one slot to another, without picking it up, that cannot // be expressed with a shift click // - Bedrock wants to pick up or place an arbitrary amount of items that cannot be expressed from // one left/right click action. - // When Bedrock does one of these actions and sends multiple packets, a 1.17.1+ server will - // increment the state ID on each confirmation packet it sends back (I.E. set slot). Then when it - // reads our next packet, because we kept the same state ID but the server incremented it, it'll be - // desynced and send the entire inventory contents back at us. - // This hack therefore increments the state ID to what the server will presumably send back to us. - // (This won't be perfect, but should get us through most vanilla situations, and if this is wrong the - // server will just send a set content packet back at us) + // Java typically doesn't increment the state ID if you send a vanilla-accurate container click packet, + // but it will increment the state ID with a vanilla client in at least the crafting table if (inventory.getContainerType() == ContainerType.CRAFTING && CraftingInventoryTranslator.isCraftingGrid(action.slot)) { // 1.18.1 sends a second set slot update for any action in the crafting grid // And an additional packet if something is removed (Mojmap: CraftingContainer#removeItem) - //TODO this code kind of really sucks; it's potentially possible to see what Bedrock sends us and send a PlaceRecipePacket int stateIdIncrements; GeyserItemStack clicked = getItem(action.slot); if (action.click == Click.LEFT) { if (!clicked.isEmpty() && !InventoryUtils.canStack(simulatedCursor, clicked)) { // An item is removed from the crafting table; yes deletion - stateIdIncrements = 3; + stateIdIncrements = 2; } else { // We can stack and we add all the items to the crafting slot; no deletion - stateIdIncrements = 2; + stateIdIncrements = 1; } } else if (action.click == Click.RIGHT) { - if (simulatedCursor.isEmpty() && !clicked.isEmpty()) { - // Items are taken; yes deletion - stateIdIncrements = 3; - } else if ((!simulatedCursor.isEmpty() && clicked.isEmpty()) || InventoryUtils.canStack(simulatedCursor, clicked)) { - // Adding our cursor item to the slot; no deletion - stateIdIncrements = 2; - } else { - // ?? nothing I guess - stateIdIncrements = 2; - } + stateIdIncrements = 1; + } else if (action.click.actionType == ContainerActionType.MOVE_TO_HOTBAR_SLOT) { + stateIdIncrements = 1; } else { if (session.getGeyser().getConfig().isDebugMode()) { session.getGeyser().getLogger().debug("Not sure how to handle state ID hack in crafting table: " + plan); } - stateIdIncrements = 2; + stateIdIncrements = 1; } inventory.incrementStateId(stateIdIncrements); - } else if (action.click.action instanceof MoveToHotbarAction) { - // Two slot changes sent - inventory.incrementStateId(2); - } else { - inventory.incrementStateId(1); } return stateId; } - //TODO private void reduceCraftingGrid(boolean makeAll) { if (gridSize == -1) return; @@ -370,9 +363,12 @@ public class ClickPlan { } for (int i = 0; i < gridSize; i++) { - GeyserItemStack item = getItem(i + 1); - if (!item.isEmpty()) - item.sub(crafted); + final int slot = i + 1; + GeyserItemStack item = getItem(slot); + if (!item.isEmpty()) { + // These changes should be broadcasted to the server + sub(slot, item, crafted); + } } } diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index 99c8c5cc4..3a097f732 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -361,6 +361,15 @@ public class GeyserSession implements GeyserConnection, CommandSender { @Setter private Int2ObjectMap stonecutterRecipes; + /** + * Starting in 1.17, Java servers expect the carriedItem parameter of the serverbound click container + * packet to be the current contents of the mouse after the transaction has been done. 1.16 expects the clicked slot + * contents before any transaction is done. With the current ViaVersion structure, if we do not send what 1.16 expects + * and send multiple click container packets, then successive transactions will be rejected. + */ + @Setter + private boolean emulatePost1_16Logic = true; + /** * The current attack speed of the player. Used for sending proper cooldown timings. * Setting a default fixes cooldowns not showing up on a fresh world. diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/BeaconInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/BeaconInventoryTranslator.java index 19d9d6de5..f194d0d3f 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/BeaconInventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/BeaconInventoryTranslator.java @@ -38,17 +38,16 @@ import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequ import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionType; import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket; import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; +import it.unimi.dsi.fastutil.ints.IntSets; import org.geysermc.geyser.inventory.BeaconContainer; +import org.geysermc.geyser.inventory.BedrockContainerSlot; import org.geysermc.geyser.inventory.Inventory; import org.geysermc.geyser.inventory.PlayerInventory; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.inventory.BedrockContainerSlot; import org.geysermc.geyser.inventory.holder.BlockInventoryHolder; import org.geysermc.geyser.inventory.updater.UIInventoryUpdater; +import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.util.InventoryUtils; -import java.util.Collections; - public class BeaconInventoryTranslator extends AbstractBlockInventoryTranslator { public BeaconInventoryTranslator() { super(1, new BlockInventoryHolder("minecraft:beacon", com.nukkitx.protocol.bedrock.data.inventory.ContainerType.BEACON) { @@ -114,7 +113,7 @@ public class BeaconInventoryTranslator extends AbstractBlockInventoryTranslator BeaconPaymentStackRequestActionData beaconPayment = (BeaconPaymentStackRequestActionData) request.getActions()[0]; ServerboundSetBeaconPacket packet = new ServerboundSetBeaconPacket(beaconPayment.getPrimaryEffect(), beaconPayment.getSecondaryEffect()); session.sendDownstreamPacket(packet); - return acceptRequest(request, makeContainerEntries(session, inventory, Collections.emptySet())); + return acceptRequest(request, makeContainerEntries(session, inventory, IntSets.emptySet())); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/CraftingInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/CraftingInventoryTranslator.java index ec3335f3c..61e2258b6 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/CraftingInventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/CraftingInventoryTranslator.java @@ -37,6 +37,11 @@ public class CraftingInventoryTranslator extends AbstractBlockInventoryTranslato super(10, "minecraft:crafting_table", ContainerType.WORKBENCH, UIInventoryUpdater.INSTANCE); } + @Override + public int getGridSize() { + return 9; + } + @Override public SlotType getSlotType(int javaSlot) { if (javaSlot == 0) { diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/EnchantingInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/EnchantingInventoryTranslator.java index 97ece79d8..800b35901 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/EnchantingInventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/EnchantingInventoryTranslator.java @@ -27,23 +27,22 @@ package org.geysermc.geyser.translator.inventory; import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundContainerButtonClickPacket; -import com.nukkitx.protocol.bedrock.data.inventory.*; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; +import com.nukkitx.protocol.bedrock.data.inventory.EnchantOptionData; +import com.nukkitx.protocol.bedrock.data.inventory.ItemStackRequest; +import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.CraftRecipeStackRequestActionData; import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionData; import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionType; import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; import com.nukkitx.protocol.bedrock.packet.PlayerEnchantOptionsPacket; -import org.geysermc.geyser.inventory.EnchantingContainer; -import org.geysermc.geyser.inventory.GeyserEnchantOption; -import org.geysermc.geyser.inventory.Inventory; -import org.geysermc.geyser.inventory.PlayerInventory; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.inventory.BedrockContainerSlot; -import org.geysermc.geyser.inventory.updater.UIInventoryUpdater; +import it.unimi.dsi.fastutil.ints.IntSets; +import org.geysermc.geyser.inventory.*; import org.geysermc.geyser.inventory.item.Enchantment; +import org.geysermc.geyser.inventory.updater.UIInventoryUpdater; +import org.geysermc.geyser.session.GeyserSession; import java.util.Arrays; -import java.util.Collections; public class EnchantingInventoryTranslator extends AbstractBlockInventoryTranslator { public EnchantingInventoryTranslator() { @@ -130,7 +129,7 @@ public class EnchantingInventoryTranslator extends AbstractBlockInventoryTransla } ServerboundContainerButtonClickPacket packet = new ServerboundContainerButtonClickPacket(inventory.getId(), javaSlot); session.sendDownstreamPacket(packet); - return acceptRequest(request, makeContainerEntries(session, inventory, Collections.emptySet())); + return acceptRequest(request, makeContainerEntries(session, inventory, IntSets.emptySet())); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/InventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/InventoryTranslator.java index e0b90db02..e6a9faf74 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/InventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/InventoryTranslator.java @@ -26,12 +26,11 @@ package org.geysermc.geyser.translator.inventory; import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; -import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; +import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType; import com.github.steveice10.mc.protocol.data.game.recipe.Ingredient; import com.github.steveice10.mc.protocol.data.game.recipe.Recipe; import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapedRecipeData; import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapelessRecipeData; -import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType; import com.github.steveice10.opennbt.tag.builtin.IntTag; import com.github.steveice10.opennbt.tag.builtin.Tag; import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; @@ -43,15 +42,10 @@ import it.unimi.dsi.fastutil.ints.*; import lombok.AllArgsConstructor; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.inventory.CartographyContainer; -import org.geysermc.geyser.inventory.GeyserItemStack; -import org.geysermc.geyser.inventory.Inventory; -import org.geysermc.geyser.inventory.PlayerInventory; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.inventory.BedrockContainerSlot; -import org.geysermc.geyser.inventory.SlotType; +import org.geysermc.geyser.inventory.*; import org.geysermc.geyser.inventory.click.Click; import org.geysermc.geyser.inventory.click.ClickPlan; +import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.inventory.chest.DoubleChestInventoryTranslator; import org.geysermc.geyser.translator.inventory.chest.SingleChestInventoryTranslator; import org.geysermc.geyser.translator.inventory.furnace.BlastFurnaceInventoryTranslator; @@ -119,6 +113,13 @@ public abstract class InventoryTranslator { public abstract SlotType getSlotType(int javaSlot); public abstract Inventory createInventory(String name, int windowId, ContainerType containerType, PlayerInventory playerInventory); + /** + * Used for crafting-related transactions. Will override in PlayerInventoryTranslator and CraftingInventoryTranslator. + */ + public int getGridSize() { + return -1; + } + /** * Should be overwritten in cases where specific inventories should reject an item being in a specific spot. * For examples, looms use this to reject items that are dyes in Bedrock but not in Java. @@ -147,7 +148,7 @@ public abstract class InventoryTranslator { return rejectRequest(request); } - public void translateRequests(GeyserSession session, Inventory inventory, List requests) { + public final void translateRequests(GeyserSession session, Inventory inventory, List requests) { boolean refresh = false; ItemStackResponsePacket responsePacket = new ItemStackResponsePacket(); for (ItemStackRequest request : requests) { @@ -199,10 +200,6 @@ public abstract class InventoryTranslator { case PLACE: { TransferStackRequestActionData transferAction = (TransferStackRequestActionData) action; if (!(checkNetId(session, inventory, transferAction.getSource()) && checkNetId(session, inventory, transferAction.getDestination()))) { - if (session.getGameMode().equals(GameMode.CREATIVE) && transferAction.getSource().getContainer() == ContainerSlotType.CRAFTING_INPUT && - transferAction.getSource().getSlot() >= 28 && transferAction.getSource().getSlot() <= 31) { - return rejectRequest(request, false); - } if (session.getGeyser().getConfig().isDebugMode()) { session.getGeyser().getLogger().error("DEBUG: About to reject TAKE/PLACE request made by " + session.name()); dumpStackRequestDetails(session, inventory, transferAction.getSource(), transferAction.getDestination()); @@ -212,17 +209,19 @@ public abstract class InventoryTranslator { int sourceSlot = bedrockSlotToJava(transferAction.getSource()); int destSlot = bedrockSlotToJava(transferAction.getDestination()); + boolean isSourceCursor = isCursor(transferAction.getSource()); + boolean isDestCursor = isCursor(transferAction.getDestination()); if (shouldRejectItemPlace(session, inventory, transferAction.getSource().getContainer(), - isCursor(transferAction.getSource()) ? -1 : sourceSlot, - transferAction.getDestination().getContainer(), isCursor(transferAction.getDestination()) ? -1 : destSlot)) { + isSourceCursor ? -1 : sourceSlot, + transferAction.getDestination().getContainer(), isDestCursor ? -1 : destSlot)) { // This item would not be here in Java return rejectRequest(request, false); } - if (isCursor(transferAction.getSource()) && isCursor(transferAction.getDestination())) { //??? + if (isSourceCursor && isDestCursor) { //??? return rejectRequest(request); - } else if (isCursor(transferAction.getSource())) { //releasing cursor + } else if (isSourceCursor) { //releasing cursor int sourceAmount = cursor.getAmount(); if (transferAction.getCount() == sourceAmount) { //release all plan.add(Click.LEFT, destSlot); @@ -231,7 +230,7 @@ public abstract class InventoryTranslator { plan.add(Click.RIGHT, destSlot); } } - } else if (isCursor(transferAction.getDestination())) { //picking up into cursor + } else if (isDestCursor) { //picking up into cursor GeyserItemStack sourceItem = plan.getItem(sourceSlot); int sourceAmount = sourceItem.getAmount(); if (cursor.isEmpty()) { //picking up into empty cursor @@ -431,6 +430,8 @@ public abstract class InventoryTranslator { int leftover = 0; ClickPlan plan = new ClickPlan(session, this, inventory); + // Track all the crafting table slots to report back the contents of the slots after crafting + IntSet affectedSlots = new IntOpenHashSet(); for (StackRequestActionData action : request.getActions()) { switch (action.getType()) { case CRAFT_RECIPE: { @@ -462,6 +463,7 @@ public abstract class InventoryTranslator { return rejectRequest(request); } craftState = CraftState.INGREDIENTS; + affectedSlots.add(bedrockSlotToJava(((ConsumeStackRequestActionData) action).getSource())); break; } case TAKE: @@ -522,21 +524,16 @@ public abstract class InventoryTranslator { } } plan.execute(false); - return acceptRequest(request, makeContainerEntries(session, inventory, plan.getAffectedSlots())); + affectedSlots.addAll(plan.getAffectedSlots()); + return acceptRequest(request, makeContainerEntries(session, inventory, affectedSlots)); } public ItemStackResponsePacket.Response translateAutoCraftingRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { - int gridSize; - int gridDimensions; - if (this instanceof PlayerInventoryTranslator) { - gridSize = 4; - gridDimensions = 2; - } else if (this instanceof CraftingInventoryTranslator) { - gridSize = 9; - gridDimensions = 3; - } else { + final int gridSize = getGridSize(); + if (gridSize == -1) { return rejectRequest(request); } + int gridDimensions = gridSize == 4 ? 2 : 3; Recipe recipe; Ingredient[] ingredients = new Ingredient[0]; @@ -722,7 +719,7 @@ public abstract class InventoryTranslator { /** * Handled in {@link PlayerInventoryTranslator} */ - public ItemStackResponsePacket.Response translateCreativeRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { + protected ItemStackResponsePacket.Response translateCreativeRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { return rejectRequest(request); } @@ -757,14 +754,14 @@ public abstract class InventoryTranslator { } } - public static ItemStackResponsePacket.Response acceptRequest(ItemStackRequest request, List containerEntries) { + protected static ItemStackResponsePacket.Response acceptRequest(ItemStackRequest request, List containerEntries) { return new ItemStackResponsePacket.Response(ItemStackResponsePacket.ResponseStatus.OK, request.getRequestId(), containerEntries); } /** * Reject an incorrect ItemStackRequest. */ - public static ItemStackResponsePacket.Response rejectRequest(ItemStackRequest request) { + protected static ItemStackResponsePacket.Response rejectRequest(ItemStackRequest request) { return rejectRequest(request, true); } @@ -774,7 +771,7 @@ public abstract class InventoryTranslator { * @param throwError whether this request was truly erroneous (true), or known as an outcome and should not be treated * as bad (false). */ - public static ItemStackResponsePacket.Response rejectRequest(ItemStackRequest request, boolean throwError) { + protected static ItemStackResponsePacket.Response rejectRequest(ItemStackRequest request, boolean throwError) { if (throwError && GeyserImpl.getInstance().getConfig().isDebugMode()) { new Throwable("DEBUGGING: ItemStackRequest rejected " + request.toString()).printStackTrace(); } @@ -849,9 +846,12 @@ public abstract class InventoryTranslator { return -1; } - public List makeContainerEntries(GeyserSession session, Inventory inventory, Set affectedSlots) { + protected final List makeContainerEntries(GeyserSession session, Inventory inventory, IntSet affectedSlots) { Map> containerMap = new HashMap<>(); - for (int slot : affectedSlots) { + // Manually call iterator to prevent Integer boxing + IntIterator it = affectedSlots.iterator(); + while (it.hasNext()) { + int slot = it.nextInt(); BedrockContainerSlot bedrockSlot = javaSlotToBedrockContainer(slot); List list = containerMap.computeIfAbsent(bedrockSlot.container(), k -> new ArrayList<>()); list.add(makeItemEntry(session, bedrockSlot.slot(), inventory.getItem(slot))); @@ -868,7 +868,7 @@ public abstract class InventoryTranslator { return containerEntries; } - public static ItemStackResponsePacket.ItemEntry makeItemEntry(GeyserSession session, int bedrockSlot, GeyserItemStack itemStack) { + private static ItemStackResponsePacket.ItemEntry makeItemEntry(GeyserSession session, int bedrockSlot, GeyserItemStack itemStack) { ItemStackResponsePacket.ItemEntry itemEntry; if (!itemStack.isEmpty()) { // As of 1.16.210: Bedrock needs confirmation on what the current item durability is. diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/PlayerInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/PlayerInventoryTranslator.java index 04de68a1e..e2349e5a5 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/PlayerInventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/PlayerInventoryTranslator.java @@ -35,6 +35,7 @@ import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.*; import com.nukkitx.protocol.bedrock.packet.InventoryContentPacket; import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; +import it.unimi.dsi.fastutil.ints.IntIterator; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; import org.geysermc.geyser.inventory.*; @@ -55,6 +56,11 @@ public class PlayerInventoryTranslator extends InventoryTranslator { super(46); } + @Override + public int getGridSize() { + return 4; + } + @Override public void updateInventory(GeyserSession session, Inventory inventory) { updateCraftingGrid(session, inventory); @@ -370,14 +376,17 @@ public class PlayerInventoryTranslator extends InventoryTranslator { } } } - for (int slot : affectedSlots) { + // Manually call iterator to prevent Integer boxing + IntIterator it = affectedSlots.iterator(); + while (it.hasNext()) { + int slot = it.nextInt(); sendCreativeAction(session, inventory, slot); } return acceptRequest(request, makeContainerEntries(session, inventory, affectedSlots)); } @Override - public ItemStackResponsePacket.Response translateCreativeRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { + protected ItemStackResponsePacket.Response translateCreativeRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { ItemStack javaCreativeItem = null; IntSet affectedSlots = new IntOpenHashSet(); CraftState craftState = CraftState.START; @@ -478,7 +487,10 @@ public class PlayerInventoryTranslator extends InventoryTranslator { return rejectRequest(request); } } - for (int slot : affectedSlots) { + // Manually call iterator to prevent Integer boxing + IntIterator it = affectedSlots.iterator(); + while (it.hasNext()) { + int slot = it.nextInt(); sendCreativeAction(session, inventory, slot); } return acceptRequest(request, makeContainerEntries(session, inventory, affectedSlots)); diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java index be10452f4..869062da2 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java @@ -25,6 +25,7 @@ package org.geysermc.geyser.translator.protocol.bedrock; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.github.steveice10.mc.protocol.data.game.entity.object.Direction; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; @@ -41,6 +42,8 @@ import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.LevelEventType; import com.nukkitx.protocol.bedrock.data.inventory.*; import com.nukkitx.protocol.bedrock.packet.*; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.type.CommandBlockMinecartEntity; import org.geysermc.geyser.entity.type.Entity; @@ -59,7 +62,6 @@ import org.geysermc.geyser.translator.sound.EntitySoundInteractionTranslator; import org.geysermc.geyser.util.BlockUtils; import org.geysermc.geyser.util.InventoryUtils; -import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; @@ -316,9 +318,13 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator changedSlots = new Int2ObjectOpenHashMap<>(2); + changedSlots.put(armorSlot, hotbarItem.getItemStack()); + changedSlots.put(bedrockHotbarSlot, armorSlotItem.getItemStack()); + ServerboundContainerClickPacket clickPacket = new ServerboundContainerClickPacket( playerInventory.getId(), playerInventory.getStateId(), armorSlot, - click.actionType, click.action, null, Collections.emptyMap()); + click.actionType, click.action, null, changedSlots); session.sendDownstreamPacket(clickPacket); } } else { diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaRecipeTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaRecipeTranslator.java index b3a04e163..da35da60e 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaRecipeTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaRecipeTranslator.java @@ -31,7 +31,7 @@ import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; -import java.util.Arrays; +import java.util.Collections; /** * Used to list recipes that we can definitely use the recipe book for (and therefore save on packet usage) @@ -42,9 +42,11 @@ public class JavaRecipeTranslator extends PacketTranslator inventorySize) { + GeyserImpl geyser = session.getGeyser(); + geyser.getLogger().warning("ClientboundContainerSetContentPacket sent to " + session.name() + + " that exceeds inventory size!"); + if (geyser.getConfig().isDebugMode()) { + geyser.getLogger().debug(packet); + geyser.getLogger().debug(inventory); + } + InventoryTranslator translator = session.getInventoryTranslator(); + if (translator != null) { + translator.updateInventory(session, inventory); + } + // 1.18.1 behavior: the previous items will be correctly set, but the state ID and carried item will not + // as this produces a stack trace on the client. + // If Java processes this correctly in the future, we can revert this behavior + return; + } + GeyserItemStack newItem = GeyserItemStack.from(packet.getItems()[i]); inventory.setItem(i, newItem, session); } @@ -55,6 +73,10 @@ public class JavaContainerSetContentTranslator extends PacketTranslator 0 || stateId != inventory.getStateId()); + inventory.setStateId(stateId); + session.getPlayerInventory().setCursor(GeyserItemStack.from(packet.getCarriedItem()), session); InventoryUtils.updateCursor(session); } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerSetSlotTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerSetSlotTranslator.java index 283d95fc4..4bb2a8e60 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerSetSlotTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerSetSlotTranslator.java @@ -30,7 +30,6 @@ import com.github.steveice10.mc.protocol.data.game.recipe.Ingredient; import com.github.steveice10.mc.protocol.data.game.recipe.Recipe; import com.github.steveice10.mc.protocol.data.game.recipe.RecipeType; import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapedRecipeData; -import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapelessRecipeData; import com.github.steveice10.mc.protocol.packet.ingame.clientbound.inventory.ClientboundContainerSetSlotPacket; import com.nukkitx.protocol.bedrock.data.inventory.ContainerId; import com.nukkitx.protocol.bedrock.data.inventory.CraftingData; @@ -40,17 +39,15 @@ import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.inventory.Inventory; import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.translator.protocol.PacketTranslator; -import org.geysermc.geyser.translator.protocol.Translator; import org.geysermc.geyser.translator.inventory.InventoryTranslator; -import org.geysermc.geyser.translator.inventory.CraftingInventoryTranslator; import org.geysermc.geyser.translator.inventory.PlayerInventoryTranslator; import org.geysermc.geyser.translator.inventory.item.ItemTranslator; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; import org.geysermc.geyser.util.InventoryUtils; import java.util.Arrays; import java.util.Collections; -import java.util.Objects; import java.util.UUID; import java.util.concurrent.TimeUnit; @@ -72,14 +69,16 @@ public class JavaContainerSetSlotTranslator extends PacketTranslator 0 || stateId != inventory.getStateId()); + inventory.setStateId(stateId); InventoryTranslator translator = session.getInventoryTranslator(); if (translator != null) { if (session.getCraftingGridFuture() != null) { session.getCraftingGridFuture().cancel(false); } - session.setCraftingGridFuture(session.scheduleInEventLoop(() -> updateCraftingGrid(session, packet, inventory, translator), 150, TimeUnit.MILLISECONDS)); + updateCraftingGrid(session, packet.getSlot(), packet.getItem(), inventory, translator); GeyserItemStack newItem = GeyserItemStack.from(packet.getItem()); if (packet.getContainerId() == 0 && !(translator instanceof PlayerInventoryTranslator)) { @@ -93,21 +92,23 @@ public class JavaContainerSetSlotTranslator extends PacketTranslator { int offset = gridSize == 4 ? 28 : 32; int gridDimensions = gridSize == 4 ? 2 : 3; int firstRow = -1, height = -1; @@ -135,62 +136,10 @@ public class JavaContainerSetSlotTranslator extends PacketTranslator null; }; } + + /** + * Test all known recipes to find a valid match + * + * @param output if not null, the recipe has to output this item + */ + @Nullable + public static Recipe getValidRecipe(final GeyserSession session, final @Nullable ItemStack output, final IntFunction inventoryGetter, + final int gridDimensions, final int firstRow, final int height, final int firstCol, final int width) { + int nonAirCount = 0; // Used for shapeless recipes for amount of items needed in recipe + for (int row = firstRow; row < height + firstRow; row++) { + for (int col = firstCol; col < width + firstCol; col++) { + if (!inventoryGetter.apply(col + (row * gridDimensions) + 1).isEmpty()) { + nonAirCount++; + } + } + } + + recipes: + for (Recipe recipe : session.getCraftingRecipes().values()) { + if (recipe.getType() == RecipeType.CRAFTING_SHAPED) { + ShapedRecipeData data = (ShapedRecipeData) recipe.getData(); + if (output != null && !data.getResult().equals(output)) { + continue; + } + Ingredient[] ingredients = data.getIngredients(); + if (data.getWidth() != width || data.getHeight() != height || width * height != ingredients.length) { + continue; + } + + if (!testShapedRecipe(ingredients, inventoryGetter, gridDimensions, firstRow, height, firstCol, width)) { + Ingredient[] mirroredIngredients = new Ingredient[ingredients.length]; + for (int row = 0; row < height; row++) { + for (int col = 0; col < width; col++) { + mirroredIngredients[col + (row * width)] = ingredients[(width - 1 - col) + (row * width)]; + } + } + + if (Arrays.equals(ingredients, mirroredIngredients) || + !testShapedRecipe(mirroredIngredients, inventoryGetter, gridDimensions, firstRow, height, firstCol, width)) { + continue; + } + } + return recipe; + } else if (recipe.getType() == RecipeType.CRAFTING_SHAPELESS) { + ShapelessRecipeData data = (ShapelessRecipeData) recipe.getData(); + if (output != null && !data.getResult().equals(output)) { + continue; + } + if (nonAirCount != data.getIngredients().length) { + // There is an amount of items on the crafting table that is not the same as the ingredient count so this is invalid + continue; + } + for (int i = 0; i < data.getIngredients().length; i++) { + Ingredient ingredient = data.getIngredients()[i]; + for (ItemStack itemStack : ingredient.getOptions()) { + boolean inventoryHasItem = false; + // Iterate only over the crafting table to find this item + crafting: + for (int row = firstRow; row < height + firstRow; row++) { + for (int col = firstCol; col < width + firstCol; col++) { + GeyserItemStack geyserItemStack = inventoryGetter.apply(col + (row * gridDimensions) + 1); + if (geyserItemStack.isEmpty()) { + inventoryHasItem = itemStack == null || itemStack.getId() == 0; + if (inventoryHasItem) { + break crafting; + } + } else if (itemStack.equals(geyserItemStack.getItemStack(1))) { + inventoryHasItem = true; + break crafting; + } + } + } + if (!inventoryHasItem) { + continue recipes; + } + } + } + return recipe; + } + } + return null; + } + + private static boolean testShapedRecipe(final Ingredient[] ingredients, final IntFunction inventoryGetter, + final int gridDimensions, final int firstRow, final int height, final int firstCol, final int width) { + int ingredientIndex = 0; + for (int row = firstRow; row < height + firstRow; row++) { + for (int col = firstCol; col < width + firstCol; col++) { + GeyserItemStack geyserItemStack = inventoryGetter.apply(col + (row * gridDimensions) + 1); + Ingredient ingredient = ingredients[ingredientIndex++]; + if (ingredient.getOptions().length == 0) { + if (!geyserItemStack.isEmpty()) { + return false; + } + } else { + boolean inventoryHasItem = false; + for (ItemStack item : ingredient.getOptions()) { + if (Objects.equals(geyserItemStack.getItemStack(1), item)) { + inventoryHasItem = true; + break; + } + } + if (!inventoryHasItem) { + return false; + } + } + } + } + return true; + } } From da33811e3b409db890abfdd2ea909e391ec27da2 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 30 Jan 2022 17:06:45 -0500 Subject: [PATCH 012/110] Init SkinProvider on Geyser startup Prevents it from loading in the middle of the first Geyser player joining --- core/src/main/java/org/geysermc/geyser/GeyserImpl.java | 2 ++ core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index f63e222cc..fd90696bd 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -62,6 +62,7 @@ import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.SessionManager; import org.geysermc.geyser.session.auth.AuthType; import org.geysermc.geyser.skin.FloodgateSkinUploader; +import org.geysermc.geyser.skin.SkinProvider; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.text.MinecraftLocale; import org.geysermc.geyser.translator.inventory.item.ItemTranslator; @@ -153,6 +154,7 @@ public class GeyserImpl implements GeyserApi { ItemTranslator.init(); MessageTranslator.init(); MinecraftLocale.init(); + SkinProvider.init(); start(); diff --git a/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java index 67364f5c6..453568341 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java @@ -819,4 +819,8 @@ public class SkinProvider { }; } } + + public static void init() { + // no-op + } } From 36afd3f2ec42e62203d6cf10c0bac0b4e5afc0b0 Mon Sep 17 00:00:00 2001 From: David Choo Date: Sun, 30 Jan 2022 19:26:31 -0500 Subject: [PATCH 013/110] Fix villager prices with demand price adjustments (#2767) * Fix villager prices with demand price adjustments * Don't cap second input/output item count * Handle negative item counts properly * Don't get item mapping twice * Add null check to getItemTag --- .../JavaMerchantOffersTranslator.java | 43 ++++++++++++++----- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaMerchantOffersTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaMerchantOffersTranslator.java index 50ee68381..8af5c8af1 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaMerchantOffersTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaMerchantOffersTranslator.java @@ -43,6 +43,7 @@ import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.inventory.item.ItemTranslator; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.util.MathUtils; import java.util.ArrayList; import java.util.List; @@ -99,16 +100,17 @@ public class JavaMerchantOffersTranslator extends PacketTranslator 0 ? packet.getVillagerLevel() - 1 : 0); // -1 crashes client - recipe.put("buyA", getItemTag(session, trade.getFirstInput(), trade.getSpecialPrice())); - if (trade.getSecondInput() != null) { - recipe.put("buyB", getItemTag(session, trade.getSecondInput(), 0)); - } + recipe.put("buyA", getItemTag(session, trade.getFirstInput(), trade.getSpecialPrice(), trade.getDemand(), trade.getPriceMultiplier())); + recipe.put("buyB", getItemTag(session, trade.getSecondInput())); recipe.putInt("uses", trade.getNumUses()); recipe.putByte("rewardExp", (byte) 1); tags.add(recipe.build()); @@ -144,12 +146,31 @@ public class JavaMerchantOffersTranslator extends PacketTranslator Date: Mon, 31 Jan 2022 09:57:43 -0500 Subject: [PATCH 014/110] Fix cache image task from last commit and make it work with reloading --- core/src/main/java/org/geysermc/geyser/GeyserImpl.java | 3 ++- .../java/org/geysermc/geyser/skin/SkinProvider.java | 10 ++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index fd90696bd..6226eec09 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -154,7 +154,6 @@ public class GeyserImpl implements GeyserApi { ItemTranslator.init(); MessageTranslator.init(); MinecraftLocale.init(); - SkinProvider.init(); start(); @@ -197,6 +196,8 @@ public class GeyserImpl implements GeyserApi { ScoreboardUpdater.init(); + SkinProvider.registerCacheImageTask(this); + ResourcePack.loadPacks(); if (platformType != PlatformType.STANDALONE && config.getRemote().getAddress().equals("auto")) { diff --git a/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java index 453568341..4383dc4e9 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java @@ -115,10 +115,12 @@ public class SkinProvider { WEARING_CUSTOM_SKULL = new SkinGeometry("{\"geometry\" :{\"default\" :\"geometry.humanoid.wearingCustomSkull\"}}", wearingCustomSkull, false); String wearingCustomSkullSlim = new String(FileUtils.readAllBytes("bedrock/skin/geometry.humanoid.wearingCustomSkullSlim.json"), StandardCharsets.UTF_8); WEARING_CUSTOM_SKULL_SLIM = new SkinGeometry("{\"geometry\" :{\"default\" :\"geometry.humanoid.wearingCustomSkullSlim\"}}", wearingCustomSkullSlim, false); + } + public static void registerCacheImageTask(GeyserImpl geyser) { // Schedule Daily Image Expiry if we are caching them - if (GeyserImpl.getInstance().getConfig().getCacheImages() > 0) { - GeyserImpl.getInstance().getScheduledThread().scheduleAtFixedRate(() -> { + if (geyser.getConfig().getCacheImages() > 0) { + geyser.getScheduledThread().scheduleAtFixedRate(() -> { File cacheFolder = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("cache").resolve("images").toFile(); if (!cacheFolder.exists()) { return; @@ -819,8 +821,4 @@ public class SkinProvider { }; } } - - public static void init() { - // no-op - } } From 9d908c5598215b2c07bf3f7c5c848680a3457b83 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 1 Feb 2022 20:15:31 -0500 Subject: [PATCH 015/110] Create IntMappedRegistry to prevent boxing --- .../geyser/registry/IntMappedRegistry.java | 122 ++++++++++++++++++ .../geysermc/geyser/registry/Registries.java | 18 ++- .../loader/CollisionRegistryLoader.java | 4 +- 3 files changed, 132 insertions(+), 12 deletions(-) create mode 100644 core/src/main/java/org/geysermc/geyser/registry/IntMappedRegistry.java diff --git a/core/src/main/java/org/geysermc/geyser/registry/IntMappedRegistry.java b/core/src/main/java/org/geysermc/geyser/registry/IntMappedRegistry.java new file mode 100644 index 000000000..892f4a6df --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/IntMappedRegistry.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2019-2022 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.registry; + +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import org.geysermc.geyser.registry.loader.RegistryLoader; +import org.jetbrains.annotations.Nullable; + +import java.util.function.Supplier; + +/** + * A mapped registry with an integer as the key. This class is designed to minimize the need for boxing/unboxing keys. + * + * @param the value + */ +public class IntMappedRegistry extends AbstractMappedRegistry> { + protected IntMappedRegistry(I input, RegistryLoader> registryLoader) { + super(input, registryLoader); + } + + /** + * Returns the value registered by the given integer. + * + * @param i the integer + * @return the value registered by the given integer. + */ + public V get(int i) { + return this.mappings.get(i); + } + + @Nullable + @Override + @Deprecated + public V get(Integer key) { + return super.get(key); + } + + /** + * Returns the value registered by the given key or the default value + * specified if null. + * + * @param i the key + * @param defaultValue the default value + * @return the value registered by the given key or the default value + * specified if null. + */ + public V getOrDefault(int i, V defaultValue) { + return this.mappings.getOrDefault(i, defaultValue); + } + + @Override + @Deprecated + public V getOrDefault(Integer key, V defaultValue) { + return super.getOrDefault(key, defaultValue); + } + + /** + * Registers a new value into this registry with the given key. + * + * @param i the key + * @param value the value + * @return a new value into this registry with the given key. + */ + public V register(int i, V value) { + return this.mappings.put(i, value); + } + + @Override + @Deprecated + public V register(Integer key, V value) { + return super.register(key, value); + } + + /** + * Creates a new integer mapped registry with the given {@link RegistryLoader}. The + * input type is not specified here, meaning the loader return type is either + * predefined, or the registry is populated at a later point. + * + * @param registryLoader the registry loader + * @param the input + * @param the map value + * @return a new registry with the given RegistryLoader + */ + public static IntMappedRegistry create(RegistryLoader> registryLoader) { + return new IntMappedRegistry<>(null, registryLoader); + } + + /** + * Creates a new integer mapped registry with the given {@link RegistryLoader} and input. + * + * @param registryLoader the registry loader + * @param the input + * @param the map value + * @return a new registry with the given RegistryLoader supplier + */ + public static IntMappedRegistry create(I input, Supplier>> registryLoader) { + return new IntMappedRegistry<>(input, registryLoader.get()); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/registry/Registries.java b/core/src/main/java/org/geysermc/geyser/registry/Registries.java index 3b0c1f138..5a60351ce 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/Registries.java +++ b/core/src/main/java/org/geysermc/geyser/registry/Registries.java @@ -31,7 +31,6 @@ import com.github.steveice10.mc.protocol.data.game.level.event.SoundEvent; import com.github.steveice10.mc.protocol.data.game.level.particle.ParticleType; import com.github.steveice10.mc.protocol.data.game.recipe.Recipe; import com.github.steveice10.mc.protocol.data.game.recipe.RecipeType; -import com.github.steveice10.mc.protocol.data.game.statistic.CustomStatistic; import com.github.steveice10.packetlib.packet.Packet; import com.nukkitx.nbt.NbtMap; import com.nukkitx.protocol.bedrock.BedrockPacket; @@ -43,26 +42,25 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import org.geysermc.geyser.entity.EntityDefinition; -import org.geysermc.geyser.registry.populator.PacketRegistryPopulator; -import org.geysermc.geyser.translator.collision.BlockCollision; import org.geysermc.geyser.inventory.item.Enchantment.JavaEnchantment; -import org.geysermc.geyser.translator.sound.SoundTranslator; -import org.geysermc.geyser.translator.sound.SoundInteractionTranslator; -import org.geysermc.geyser.translator.level.block.entity.BlockEntityTranslator; -import org.geysermc.geyser.translator.level.event.LevelEventTranslator; import org.geysermc.geyser.registry.loader.*; import org.geysermc.geyser.registry.populator.ItemRegistryPopulator; +import org.geysermc.geyser.registry.populator.PacketRegistryPopulator; import org.geysermc.geyser.registry.populator.RecipeRegistryPopulator; import org.geysermc.geyser.registry.type.EnchantmentData; import org.geysermc.geyser.registry.type.ItemMappings; import org.geysermc.geyser.registry.type.ParticleMapping; import org.geysermc.geyser.registry.type.SoundMapping; +import org.geysermc.geyser.translator.collision.BlockCollision; +import org.geysermc.geyser.translator.level.block.entity.BlockEntityTranslator; +import org.geysermc.geyser.translator.level.event.LevelEventTranslator; +import org.geysermc.geyser.translator.sound.SoundInteractionTranslator; +import org.geysermc.geyser.translator.sound.SoundTranslator; import java.util.EnumMap; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.function.IntFunction; /** * Holds all the common registries in Geyser. @@ -96,7 +94,7 @@ public final class Registries { /** * A mapped registry containing which holds block IDs to its {@link BlockCollision}. */ - public static final SimpleMappedRegistry COLLISIONS = SimpleMappedRegistry.create(Pair.of("org.geysermc.geyser.translator.collision.CollisionRemapper", "mappings/collision.json"), CollisionRegistryLoader::new); + public static final IntMappedRegistry COLLISIONS = IntMappedRegistry.create(Pair.of("org.geysermc.geyser.translator.collision.CollisionRemapper", "mappings/collision.json"), CollisionRegistryLoader::new); /** * A versioned registry which holds a {@link RecipeType} to a corresponding list of {@link CraftingData}. @@ -149,7 +147,7 @@ public final class Registries { * A mapped registry holding the available records, with the ID of the record being the key, and the {@link com.nukkitx.protocol.bedrock.data.SoundEvent} * as the value. */ - public static final SimpleMappedRegistry RECORDS = SimpleMappedRegistry.create(RegistryLoaders.empty(Int2ObjectOpenHashMap::new)); + public static final IntMappedRegistry RECORDS = IntMappedRegistry.create(RegistryLoaders.empty(Int2ObjectOpenHashMap::new)); /** * A mapped registry holding sound identifiers to their corresponding {@link SoundMapping}. diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/CollisionRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/CollisionRegistryLoader.java index c6cd092d4..b74573a4e 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/loader/CollisionRegistryLoader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/CollisionRegistryLoader.java @@ -50,10 +50,10 @@ import java.util.regex.Pattern; /** * Loads collision data from the given resource path. */ -public class CollisionRegistryLoader extends MultiResourceRegistryLoader> { +public class CollisionRegistryLoader extends MultiResourceRegistryLoader> { @Override - public Map load(Pair input) { + public Int2ObjectMap load(Pair input) { Int2ObjectMap collisions = new Int2ObjectOpenHashMap<>(); Map, CollisionInfo> annotationMap = new IdentityHashMap<>(); From c295e47940aeed0b913d3836973e410ba422aab1 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 5 Feb 2022 17:12:00 -0500 Subject: [PATCH 016/110] Ensure inventory affected slot is added in MOVE_TO_HOTBAR actions --- .../java/org/geysermc/geyser/inventory/click/ClickPlan.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/src/main/java/org/geysermc/geyser/inventory/click/ClickPlan.java b/core/src/main/java/org/geysermc/geyser/inventory/click/ClickPlan.java index e6eeea689..45d535167 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/click/ClickPlan.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/click/ClickPlan.java @@ -28,6 +28,7 @@ package org.geysermc.geyser.inventory.click; import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.github.steveice10.mc.protocol.data.game.inventory.ContainerActionType; import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType; +import com.github.steveice10.mc.protocol.data.game.inventory.MoveToHotbarAction; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundContainerClickPacket; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; @@ -381,6 +382,10 @@ public final class ClickPlan { for (ClickAction action : plan) { if (translator.getSlotType(action.slot) == SlotType.NORMAL && action.slot != Click.OUTSIDE_SLOT) { affectedSlots.add(action.slot); + if (action.click.actionType == ContainerActionType.MOVE_TO_HOTBAR_SLOT) { + //TODO won't work if offhand is added + affectedSlots.add(inventory.getOffsetForHotbar(((MoveToHotbarAction) action.click.action).ordinal())); + } } } return affectedSlots; From 08a78731dfa950f8a4cb8f3009de366f86ef79a7 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 6 Feb 2022 17:15:12 -0500 Subject: [PATCH 017/110] Drop 1.17.30 support; add support for 1.18.10.28 beta --- core/pom.xml | 4 +- .../geyser/network/MinecraftProtocol.java | 8 +- .../populator/BlockRegistryPopulator.java | 37 +- .../populator/ItemRegistryPopulator.java | 4 +- .../bedrock/block_palette.1_17_30.nbt | Bin 41709 -> 0 bytes .../bedrock/block_palette.1_18_10.nbt | Bin 0 -> 41783 bytes ...17_30.json => creative_items.1_18_10.json} | 1786 +++++++++-------- ....json => runtime_item_states.1_18_10.json} | 168 +- 8 files changed, 1047 insertions(+), 960 deletions(-) delete mode 100644 core/src/main/resources/bedrock/block_palette.1_17_30.nbt create mode 100644 core/src/main/resources/bedrock/block_palette.1_18_10.nbt rename core/src/main/resources/bedrock/{creative_items.1_17_30.json => creative_items.1_18_10.json} (91%) rename core/src/main/resources/bedrock/{runtime_item_states.1_17_30.json => runtime_item_states.1_18_10.json} (98%) diff --git a/core/pom.xml b/core/pom.xml index 4da6bdbe0..c2e8b5f5b 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -120,8 +120,8 @@ com.github.CloudburstMC.Protocol - bedrock-v475 - c22aa595 + bedrock-v486 + v1.18.10-c2c5a7069f-1 compile diff --git a/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java b/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java index f605f9089..c4bd05b13 100644 --- a/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java +++ b/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java @@ -28,9 +28,9 @@ package org.geysermc.geyser.network; import com.github.steveice10.mc.protocol.codec.MinecraftCodec; import com.github.steveice10.mc.protocol.codec.PacketCodec; import com.nukkitx.protocol.bedrock.BedrockPacketCodec; -import com.nukkitx.protocol.bedrock.v465.Bedrock_v465; import com.nukkitx.protocol.bedrock.v471.Bedrock_v471; import com.nukkitx.protocol.bedrock.v475.Bedrock_v475; +import com.nukkitx.protocol.bedrock.v486.Bedrock_v486; import java.util.ArrayList; import java.util.Arrays; @@ -45,7 +45,7 @@ public final class MinecraftProtocol { * Default Bedrock codec that should act as a fallback. Should represent the latest available * release of the game that Geyser supports. */ - public static final BedrockPacketCodec DEFAULT_BEDROCK_CODEC = Bedrock_v475.V475_CODEC; + public static final BedrockPacketCodec DEFAULT_BEDROCK_CODEC = Bedrock_v486.V486_CODEC; /** * A list of all supported Bedrock versions that can join Geyser */ @@ -58,9 +58,9 @@ public final class MinecraftProtocol { private static final PacketCodec DEFAULT_JAVA_CODEC = MinecraftCodec.CODEC; static { - SUPPORTED_BEDROCK_CODECS.add(Bedrock_v465.V465_CODEC); SUPPORTED_BEDROCK_CODECS.add(Bedrock_v471.V471_CODEC); - SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC.toBuilder().minecraftVersion("1.18.0/1.18.1/1.18.2").build()); + SUPPORTED_BEDROCK_CODECS.add(Bedrock_v475.V475_CODEC.toBuilder().minecraftVersion("1.18.0/1.18.1/1.18.2").build()); + SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC); } /** diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java index b1066c79c..8238bcea1 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java @@ -28,8 +28,8 @@ package org.geysermc.geyser.registry.populator; import com.fasterxml.jackson.databind.JsonNode; import com.google.common.collect.ImmutableMap; import com.nukkitx.nbt.*; -import com.nukkitx.protocol.bedrock.v465.Bedrock_v465; import com.nukkitx.protocol.bedrock.v471.Bedrock_v471; +import com.nukkitx.protocol.bedrock.v486.Bedrock_v486; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.objects.Object2IntMap; @@ -46,7 +46,10 @@ import org.geysermc.geyser.util.BlockUtils; import java.io.DataInputStream; import java.io.InputStream; -import java.util.*; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.Iterator; +import java.util.Map; import java.util.function.BiFunction; import java.util.zip.GZIPInputStream; @@ -59,8 +62,34 @@ public class BlockRegistryPopulator { static { ImmutableMap.Builder, BiFunction> stateMapperBuilder = ImmutableMap., BiFunction>builder() - .put(ObjectIntPair.of("1_17_30", Bedrock_v465.V465_CODEC.getProtocolVersion()), EMPTY_MAPPER) - .put(ObjectIntPair.of("1_17_40", Bedrock_v471.V471_CODEC.getProtocolVersion()), EMPTY_MAPPER); + .put(ObjectIntPair.of("1_17_40", Bedrock_v471.V471_CODEC.getProtocolVersion()), EMPTY_MAPPER) + .put(ObjectIntPair.of("1_18_10", Bedrock_v486.V486_CODEC.getProtocolVersion()), (bedrockIdentifier, statesBuilder) -> { + statesBuilder.remove("no_drop_bit"); // Used in skulls + if (bedrockIdentifier.equals("minecraft:glow_lichen")) { + // Moved around north, south, west + int bits = (int) statesBuilder.get("multi_face_direction_bits"); + boolean north = (bits & (1 << 2)) != 0; + boolean south = (bits & (1 << 3)) != 0; + boolean west = (bits & (1 << 4)) != 0; + if (north) { + bits |= 1 << 4; + } else { + bits &= ~(1 << 4); + } + if (south) { + bits |= 1 << 2; + } else { + bits &= ~(1 << 2); + } + if (west) { + bits |= 1 << 3; + } else { + bits &= ~(1 << 3); + } + statesBuilder.put("multi_face_direction_bits", bits); + } + return null; + }); BLOCK_MAPPERS = stateMapperBuilder.build(); } diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java index d448bfa6a..1b56a83de 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java @@ -35,9 +35,9 @@ import com.nukkitx.protocol.bedrock.data.SoundEvent; import com.nukkitx.protocol.bedrock.data.inventory.ComponentItemData; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.packet.StartGamePacket; -import com.nukkitx.protocol.bedrock.v465.Bedrock_v465; import com.nukkitx.protocol.bedrock.v471.Bedrock_v471; import com.nukkitx.protocol.bedrock.v475.Bedrock_v475; +import com.nukkitx.protocol.bedrock.v486.Bedrock_v486; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.IntArrayList; @@ -63,9 +63,9 @@ public class ItemRegistryPopulator { static { PALETTE_VERSIONS = new Object2ObjectOpenHashMap<>(); - PALETTE_VERSIONS.put("1_17_30", new PaletteVersion(Bedrock_v465.V465_CODEC.getProtocolVersion(), Collections.emptyMap())); PALETTE_VERSIONS.put("1_17_40", new PaletteVersion(Bedrock_v471.V471_CODEC.getProtocolVersion(), Collections.emptyMap())); PALETTE_VERSIONS.put("1_18_0", new PaletteVersion(Bedrock_v475.V475_CODEC.getProtocolVersion(), Collections.emptyMap())); + PALETTE_VERSIONS.put("1_18_10", new PaletteVersion(Bedrock_v486.V486_CODEC.getProtocolVersion(), Collections.emptyMap())); } private record PaletteVersion(int protocolVersion, Map additionalTranslatedItems) { diff --git a/core/src/main/resources/bedrock/block_palette.1_17_30.nbt b/core/src/main/resources/bedrock/block_palette.1_17_30.nbt deleted file mode 100644 index fde145ca5239cc9a74e13b2a4a2ac77fca4971f0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 41709 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 ed5@XQvQCl&fW^!*ULHbXSE{BK6G5 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 DvTLIl literal 0 HcmV?d00001 diff --git a/core/src/main/resources/bedrock/creative_items.1_17_30.json b/core/src/main/resources/bedrock/creative_items.1_18_10.json similarity index 91% rename from core/src/main/resources/bedrock/creative_items.1_17_30.json rename to core/src/main/resources/bedrock/creative_items.1_18_10.json index bb73854dc..dadabf91f 100644 --- a/core/src/main/resources/bedrock/creative_items.1_17_30.json +++ b/core/src/main/resources/bedrock/creative_items.1_18_10.json @@ -2,39 +2,35 @@ "items" : [ { "id" : "minecraft:planks", - "blockRuntimeId" : 5794 + "blockRuntimeId" : 5800 }, { "id" : "minecraft:planks", - "blockRuntimeId" : 5795 + "blockRuntimeId" : 5801 }, { "id" : "minecraft:planks", - "blockRuntimeId" : 5796 + "blockRuntimeId" : 5802 }, { "id" : "minecraft:planks", - "blockRuntimeId" : 5797 + "blockRuntimeId" : 5803 }, { "id" : "minecraft:planks", - "blockRuntimeId" : 5798 + "blockRuntimeId" : 5804 }, { "id" : "minecraft:planks", - "blockRuntimeId" : 5799 + "blockRuntimeId" : 5805 }, { "id" : "minecraft:crimson_planks", - "blockRuntimeId" : 3839 + "blockRuntimeId" : 3840 }, { "id" : "minecraft:warped_planks", - "blockRuntimeId" : 7594 - }, - { - "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1318 + "blockRuntimeId" : 7596 }, { "id" : "minecraft:cobblestone_wall", @@ -56,69 +52,69 @@ "id" : "minecraft:cobblestone_wall", "blockRuntimeId" : 1323 }, - { - "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1330 - }, - { - "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1325 - }, - { - "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1326 - }, { "id" : "minecraft:cobblestone_wall", "blockRuntimeId" : 1324 }, - { - "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 1327 - }, { "id" : "minecraft:cobblestone_wall", "blockRuntimeId" : 1331 }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1326 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1327 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1325 + }, { "id" : "minecraft:cobblestone_wall", "blockRuntimeId" : 1328 }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1332 + }, { "id" : "minecraft:cobblestone_wall", "blockRuntimeId" : 1329 }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1330 + }, { "id" : "minecraft:blackstone_wall", "blockRuntimeId" : 507 }, { "id" : "minecraft:polished_blackstone_wall", - "blockRuntimeId" : 6038 + "blockRuntimeId" : 6044 }, { "id" : "minecraft:polished_blackstone_brick_wall", - "blockRuntimeId" : 5835 + "blockRuntimeId" : 5841 }, { "id" : "minecraft:cobbled_deepslate_wall", - "blockRuntimeId" : 1155 + "blockRuntimeId" : 1156 }, { "id" : "minecraft:deepslate_tile_wall", - "blockRuntimeId" : 4297 + "blockRuntimeId" : 4298 }, { "id" : "minecraft:polished_deepslate_wall", - "blockRuntimeId" : 6213 + "blockRuntimeId" : 6219 }, { "id" : "minecraft:deepslate_brick_wall", - "blockRuntimeId" : 4114 - }, - { - "id" : "minecraft:fence", - "blockRuntimeId" : 4773 + "blockRuntimeId" : 4115 }, { "id" : "minecraft:fence", @@ -141,24 +137,28 @@ "blockRuntimeId" : 4778 }, { - "id" : "minecraft:nether_brick_fence", - "blockRuntimeId" : 5686 - }, - { - "id" : "minecraft:crimson_fence", - "blockRuntimeId" : 3817 - }, - { - "id" : "minecraft:warped_fence", - "blockRuntimeId" : 7572 - }, - { - "id" : "minecraft:fence_gate", + "id" : "minecraft:fence", "blockRuntimeId" : 4779 }, + { + "id" : "minecraft:nether_brick_fence", + "blockRuntimeId" : 5690 + }, + { + "id" : "minecraft:crimson_fence", + "blockRuntimeId" : 3818 + }, + { + "id" : "minecraft:warped_fence", + "blockRuntimeId" : 7574 + }, + { + "id" : "minecraft:fence_gate", + "blockRuntimeId" : 4780 + }, { "id" : "minecraft:spruce_fence_gate", - "blockRuntimeId" : 7006 + "blockRuntimeId" : 7007 }, { "id" : "minecraft:birch_fence_gate", @@ -166,7 +166,7 @@ }, { "id" : "minecraft:jungle_fence_gate", - "blockRuntimeId" : 5252 + "blockRuntimeId" : 5254 }, { "id" : "minecraft:acacia_fence_gate", @@ -174,35 +174,35 @@ }, { "id" : "minecraft:dark_oak_fence_gate", - "blockRuntimeId" : 3980 + "blockRuntimeId" : 3981 }, { "id" : "minecraft:crimson_fence_gate", - "blockRuntimeId" : 3818 + "blockRuntimeId" : 3819 }, { "id" : "minecraft:warped_fence_gate", - "blockRuntimeId" : 7573 + "blockRuntimeId" : 7575 }, { "id" : "minecraft:normal_stone_stairs", - "blockRuntimeId" : 5705 + "blockRuntimeId" : 5709 }, { "id" : "minecraft:stone_stairs", - "blockRuntimeId" : 7277 + "blockRuntimeId" : 7278 }, { "id" : "minecraft:mossy_cobblestone_stairs", - "blockRuntimeId" : 5667 + "blockRuntimeId" : 5669 }, { "id" : "minecraft:oak_stairs", - "blockRuntimeId" : 5714 + "blockRuntimeId" : 5718 }, { "id" : "minecraft:spruce_stairs", - "blockRuntimeId" : 7038 + "blockRuntimeId" : 7039 }, { "id" : "minecraft:birch_stairs", @@ -210,7 +210,7 @@ }, { "id" : "minecraft:jungle_stairs", - "blockRuntimeId" : 5284 + "blockRuntimeId" : 5286 }, { "id" : "minecraft:acacia_stairs", @@ -218,47 +218,47 @@ }, { "id" : "minecraft:dark_oak_stairs", - "blockRuntimeId" : 4012 + "blockRuntimeId" : 4013 }, { "id" : "minecraft:stone_brick_stairs", - "blockRuntimeId" : 7183 + "blockRuntimeId" : 7184 }, { "id" : "minecraft:mossy_stone_brick_stairs", - "blockRuntimeId" : 5675 + "blockRuntimeId" : 5677 }, { "id" : "minecraft:sandstone_stairs", - "blockRuntimeId" : 6707 + "blockRuntimeId" : 6713 }, { "id" : "minecraft:smooth_sandstone_stairs", - "blockRuntimeId" : 6899 + "blockRuntimeId" : 6900 }, { "id" : "minecraft:red_sandstone_stairs", - "blockRuntimeId" : 6634 + "blockRuntimeId" : 6640 }, { "id" : "minecraft:smooth_red_sandstone_stairs", - "blockRuntimeId" : 6891 + "blockRuntimeId" : 6892 }, { "id" : "minecraft:granite_stairs", - "blockRuntimeId" : 4988 + "blockRuntimeId" : 4990 }, { "id" : "minecraft:polished_granite_stairs", - "blockRuntimeId" : 6383 + "blockRuntimeId" : 6389 }, { "id" : "minecraft:diorite_stairs", - "blockRuntimeId" : 4475 + "blockRuntimeId" : 4476 }, { "id" : "minecraft:polished_diorite_stairs", - "blockRuntimeId" : 6375 + "blockRuntimeId" : 6381 }, { "id" : "minecraft:andesite_stairs", @@ -266,7 +266,7 @@ }, { "id" : "minecraft:polished_andesite_stairs", - "blockRuntimeId" : 5811 + "blockRuntimeId" : 5817 }, { "id" : "minecraft:brick_stairs", @@ -274,47 +274,47 @@ }, { "id" : "minecraft:nether_brick_stairs", - "blockRuntimeId" : 5687 + "blockRuntimeId" : 5691 }, { "id" : "minecraft:red_nether_brick_stairs", - "blockRuntimeId" : 6622 + "blockRuntimeId" : 6628 }, { "id" : "minecraft:end_brick_stairs", - "blockRuntimeId" : 4719 + "blockRuntimeId" : 4720 }, { "id" : "minecraft:quartz_stairs", - "blockRuntimeId" : 6556 + "blockRuntimeId" : 6562 }, { "id" : "minecraft:smooth_quartz_stairs", - "blockRuntimeId" : 6883 + "blockRuntimeId" : 6884 }, { "id" : "minecraft:purpur_stairs", - "blockRuntimeId" : 6534 + "blockRuntimeId" : 6540 }, { "id" : "minecraft:prismarine_stairs", - "blockRuntimeId" : 6446 + "blockRuntimeId" : 6452 }, { "id" : "minecraft:dark_prismarine_stairs", - "blockRuntimeId" : 4036 + "blockRuntimeId" : 4037 }, { "id" : "minecraft:prismarine_bricks_stairs", - "blockRuntimeId" : 6438 + "blockRuntimeId" : 6444 }, { "id" : "minecraft:crimson_stairs", - "blockRuntimeId" : 3859 + "blockRuntimeId" : 3860 }, { "id" : "minecraft:warped_stairs", - "blockRuntimeId" : 7614 + "blockRuntimeId" : 7616 }, { "id" : "minecraft:blackstone_stairs", @@ -322,59 +322,59 @@ }, { "id" : "minecraft:polished_blackstone_stairs", - "blockRuntimeId" : 6030 + "blockRuntimeId" : 6036 }, { "id" : "minecraft:polished_blackstone_brick_stairs", - "blockRuntimeId" : 5827 + "blockRuntimeId" : 5833 }, { "id" : "minecraft:cut_copper_stairs", - "blockRuntimeId" : 3912 + "blockRuntimeId" : 3913 }, { "id" : "minecraft:exposed_cut_copper_stairs", - "blockRuntimeId" : 4755 + "blockRuntimeId" : 4756 }, { "id" : "minecraft:weathered_cut_copper_stairs", - "blockRuntimeId" : 7741 + "blockRuntimeId" : 7743 }, { "id" : "minecraft:oxidized_cut_copper_stairs", - "blockRuntimeId" : 5755 + "blockRuntimeId" : 5760 }, { "id" : "minecraft:waxed_cut_copper_stairs", - "blockRuntimeId" : 7685 + "blockRuntimeId" : 7687 }, { "id" : "minecraft:waxed_exposed_cut_copper_stairs", - "blockRuntimeId" : 7699 + "blockRuntimeId" : 7701 }, { "id" : "minecraft:waxed_weathered_cut_copper_stairs", - "blockRuntimeId" : 7727 + "blockRuntimeId" : 7729 }, { "id" : "minecraft:waxed_oxidized_cut_copper_stairs", - "blockRuntimeId" : 7713 + "blockRuntimeId" : 7715 }, { "id" : "minecraft:cobbled_deepslate_stairs", - "blockRuntimeId" : 1147 + "blockRuntimeId" : 1148 }, { "id" : "minecraft:deepslate_tile_stairs", - "blockRuntimeId" : 4289 + "blockRuntimeId" : 4290 }, { "id" : "minecraft:polished_deepslate_stairs", - "blockRuntimeId" : 6205 + "blockRuntimeId" : 6211 }, { "id" : "minecraft:deepslate_brick_stairs", - "blockRuntimeId" : 4106 + "blockRuntimeId" : 4107 }, { "id" : "minecraft:wooden_door" @@ -405,11 +405,11 @@ }, { "id" : "minecraft:trapdoor", - "blockRuntimeId" : 7359 + "blockRuntimeId" : 7360 }, { "id" : "minecraft:spruce_trapdoor", - "blockRuntimeId" : 7062 + "blockRuntimeId" : 7063 }, { "id" : "minecraft:birch_trapdoor", @@ -417,7 +417,7 @@ }, { "id" : "minecraft:jungle_trapdoor", - "blockRuntimeId" : 5308 + "blockRuntimeId" : 5310 }, { "id" : "minecraft:acacia_trapdoor", @@ -425,51 +425,27 @@ }, { "id" : "minecraft:dark_oak_trapdoor", - "blockRuntimeId" : 4020 + "blockRuntimeId" : 4021 }, { "id" : "minecraft:iron_trapdoor", - "blockRuntimeId" : 5167 + "blockRuntimeId" : 5169 }, { "id" : "minecraft:crimson_trapdoor", - "blockRuntimeId" : 3886 + "blockRuntimeId" : 3887 }, { "id" : "minecraft:warped_trapdoor", - "blockRuntimeId" : 7641 + "blockRuntimeId" : 7643 }, { "id" : "minecraft:iron_bars", - "blockRuntimeId" : 5132 + "blockRuntimeId" : 5134 }, { "id" : "minecraft:glass", - "blockRuntimeId" : 4882 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 7084 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 7092 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 7091 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 7099 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 7096 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 7098 + "blockRuntimeId" : 4884 }, { "id" : "minecraft:stained_glass", @@ -477,11 +453,15 @@ }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 7088 + "blockRuntimeId" : 7093 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 7089 + "blockRuntimeId" : 7092 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 7100 }, { "id" : "minecraft:stained_glass", @@ -489,59 +469,55 @@ }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 7093 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 7087 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 7095 - }, - { - "id" : "minecraft:stained_glass", - "blockRuntimeId" : 7094 + "blockRuntimeId" : 7099 }, { "id" : "minecraft:stained_glass", "blockRuntimeId" : 7086 }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 7089 + }, { "id" : "minecraft:stained_glass", "blockRuntimeId" : 7090 }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 7098 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 7094 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 7088 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 7096 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 7095 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 7087 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 7091 + }, { "id" : "minecraft:tinted_glass", - "blockRuntimeId" : 7348 + "blockRuntimeId" : 7349 }, { "id" : "minecraft:glass_pane", - "blockRuntimeId" : 4883 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 7100 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 7108 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 7107 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 7115 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 7112 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 7114 + "blockRuntimeId" : 4885 }, { "id" : "minecraft:stained_glass_pane", @@ -549,11 +525,15 @@ }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 7104 + "blockRuntimeId" : 7109 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 7105 + "blockRuntimeId" : 7108 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7116 }, { "id" : "minecraft:stained_glass_pane", @@ -561,59 +541,71 @@ }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 7109 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 7103 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 7111 - }, - { - "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 7110 + "blockRuntimeId" : 7115 }, { "id" : "minecraft:stained_glass_pane", "blockRuntimeId" : 7102 }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7105 + }, { "id" : "minecraft:stained_glass_pane", "blockRuntimeId" : 7106 }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7114 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7110 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7104 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7112 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7111 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7103 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7107 + }, { "id" : "minecraft:ladder", - "blockRuntimeId" : 5356 + "blockRuntimeId" : 5358 }, { "id" : "minecraft:scaffolding", - "blockRuntimeId" : 6727 + "blockRuntimeId" : 6733 }, { "id" : "minecraft:double_stone_slab", - "blockRuntimeId" : 7219 + "blockRuntimeId" : 7220 }, { "id" : "minecraft:double_stone_slab4", - "blockRuntimeId" : 7269 + "blockRuntimeId" : 7270 }, { "id" : "minecraft:double_stone_slab", - "blockRuntimeId" : 7222 + "blockRuntimeId" : 7223 }, { "id" : "minecraft:double_stone_slab2", - "blockRuntimeId" : 7240 - }, - { - "id" : "minecraft:wooden_slab", - "blockRuntimeId" : 7899 - }, - { - "id" : "minecraft:wooden_slab", - "blockRuntimeId" : 7900 + "blockRuntimeId" : 7241 }, { "id" : "minecraft:wooden_slab", @@ -632,76 +624,12 @@ "blockRuntimeId" : 7904 }, { - "id" : "minecraft:double_stone_slab", - "blockRuntimeId" : 7224 + "id" : "minecraft:wooden_slab", + "blockRuntimeId" : 7905 }, { - "id" : "minecraft:double_stone_slab4", - "blockRuntimeId" : 7267 - }, - { - "id" : "minecraft:double_stone_slab", - "blockRuntimeId" : 7220 - }, - { - "id" : "minecraft:double_stone_slab4", - "blockRuntimeId" : 7270 - }, - { - "id" : "minecraft:double_stone_slab2", - "blockRuntimeId" : 7241 - }, - { - "id" : "minecraft:double_stone_slab2", - "blockRuntimeId" : 7235 - }, - { - "id" : "minecraft:double_stone_slab4", - "blockRuntimeId" : 7271 - }, - { - "id" : "minecraft:double_stone_slab3", - "blockRuntimeId" : 7252 - }, - { - "id" : "minecraft:double_stone_slab3", - "blockRuntimeId" : 7257 - }, - { - "id" : "minecraft:double_stone_slab3", - "blockRuntimeId" : 7258 - }, - { - "id" : "minecraft:double_stone_slab3", - "blockRuntimeId" : 7255 - }, - { - "id" : "minecraft:double_stone_slab3", - "blockRuntimeId" : 7256 - }, - { - "id" : "minecraft:double_stone_slab3", - "blockRuntimeId" : 7254 - }, - { - "id" : "minecraft:double_stone_slab3", - "blockRuntimeId" : 7253 - }, - { - "id" : "minecraft:double_stone_slab", - "blockRuntimeId" : 7223 - }, - { - "id" : "minecraft:double_stone_slab", - "blockRuntimeId" : 7226 - }, - { - "id" : "minecraft:double_stone_slab2", - "blockRuntimeId" : 7242 - }, - { - "id" : "minecraft:double_stone_slab3", - "blockRuntimeId" : 7251 + "id" : "minecraft:wooden_slab", + "blockRuntimeId" : 7906 }, { "id" : "minecraft:double_stone_slab", @@ -711,10 +639,78 @@ "id" : "minecraft:double_stone_slab4", "blockRuntimeId" : 7268 }, + { + "id" : "minecraft:double_stone_slab", + "blockRuntimeId" : 7221 + }, + { + "id" : "minecraft:double_stone_slab4", + "blockRuntimeId" : 7271 + }, + { + "id" : "minecraft:double_stone_slab2", + "blockRuntimeId" : 7242 + }, { "id" : "minecraft:double_stone_slab2", "blockRuntimeId" : 7236 }, + { + "id" : "minecraft:double_stone_slab4", + "blockRuntimeId" : 7272 + }, + { + "id" : "minecraft:double_stone_slab3", + "blockRuntimeId" : 7253 + }, + { + "id" : "minecraft:double_stone_slab3", + "blockRuntimeId" : 7258 + }, + { + "id" : "minecraft:double_stone_slab3", + "blockRuntimeId" : 7259 + }, + { + "id" : "minecraft:double_stone_slab3", + "blockRuntimeId" : 7256 + }, + { + "id" : "minecraft:double_stone_slab3", + "blockRuntimeId" : 7257 + }, + { + "id" : "minecraft:double_stone_slab3", + "blockRuntimeId" : 7255 + }, + { + "id" : "minecraft:double_stone_slab3", + "blockRuntimeId" : 7254 + }, + { + "id" : "minecraft:double_stone_slab", + "blockRuntimeId" : 7224 + }, + { + "id" : "minecraft:double_stone_slab", + "blockRuntimeId" : 7227 + }, + { + "id" : "minecraft:double_stone_slab2", + "blockRuntimeId" : 7243 + }, + { + "id" : "minecraft:double_stone_slab3", + "blockRuntimeId" : 7252 + }, + { + "id" : "minecraft:double_stone_slab", + "blockRuntimeId" : 7226 + }, + { + "id" : "minecraft:double_stone_slab4", + "blockRuntimeId" : 7269 + }, { "id" : "minecraft:double_stone_slab2", "blockRuntimeId" : 7237 @@ -727,13 +723,17 @@ "id" : "minecraft:double_stone_slab2", "blockRuntimeId" : 7239 }, + { + "id" : "minecraft:double_stone_slab2", + "blockRuntimeId" : 7240 + }, { "id" : "minecraft:crimson_slab", - "blockRuntimeId" : 3857 + "blockRuntimeId" : 3858 }, { "id" : "minecraft:warped_slab", - "blockRuntimeId" : 7612 + "blockRuntimeId" : 7614 }, { "id" : "minecraft:blackstone_slab", @@ -741,59 +741,59 @@ }, { "id" : "minecraft:polished_blackstone_slab", - "blockRuntimeId" : 6028 + "blockRuntimeId" : 6034 }, { "id" : "minecraft:polished_blackstone_brick_slab", - "blockRuntimeId" : 5825 + "blockRuntimeId" : 5831 }, { "id" : "minecraft:cut_copper_slab", - "blockRuntimeId" : 3910 + "blockRuntimeId" : 3911 }, { "id" : "minecraft:exposed_cut_copper_slab", - "blockRuntimeId" : 4753 + "blockRuntimeId" : 4754 }, { "id" : "minecraft:weathered_cut_copper_slab", - "blockRuntimeId" : 7739 + "blockRuntimeId" : 7741 }, { "id" : "minecraft:oxidized_cut_copper_slab", - "blockRuntimeId" : 5753 + "blockRuntimeId" : 5758 }, { "id" : "minecraft:waxed_cut_copper_slab", - "blockRuntimeId" : 7683 + "blockRuntimeId" : 7685 }, { "id" : "minecraft:waxed_exposed_cut_copper_slab", - "blockRuntimeId" : 7697 + "blockRuntimeId" : 7699 }, { "id" : "minecraft:waxed_weathered_cut_copper_slab", - "blockRuntimeId" : 7725 + "blockRuntimeId" : 7727 }, { "id" : "minecraft:waxed_oxidized_cut_copper_slab", - "blockRuntimeId" : 7711 + "blockRuntimeId" : 7713 }, { "id" : "minecraft:cobbled_deepslate_slab", - "blockRuntimeId" : 1145 + "blockRuntimeId" : 1146 }, { "id" : "minecraft:polished_deepslate_slab", - "blockRuntimeId" : 6203 + "blockRuntimeId" : 6209 }, { "id" : "minecraft:deepslate_tile_slab", - "blockRuntimeId" : 4287 + "blockRuntimeId" : 4288 }, { "id" : "minecraft:deepslate_brick_slab", - "blockRuntimeId" : 4104 + "blockRuntimeId" : 4105 }, { "id" : "minecraft:brick_block", @@ -805,15 +805,11 @@ }, { "id" : "minecraft:cracked_nether_bricks", - "blockRuntimeId" : 3768 + "blockRuntimeId" : 3769 }, { "id" : "minecraft:quartz_bricks", - "blockRuntimeId" : 6554 - }, - { - "id" : "minecraft:stonebrick", - "blockRuntimeId" : 7285 + "blockRuntimeId" : 6560 }, { "id" : "minecraft:stonebrick", @@ -827,25 +823,29 @@ "id" : "minecraft:stonebrick", "blockRuntimeId" : 7288 }, + { + "id" : "minecraft:stonebrick", + "blockRuntimeId" : 7289 + }, { "id" : "minecraft:end_bricks", - "blockRuntimeId" : 4727 + "blockRuntimeId" : 4728 }, { "id" : "minecraft:prismarine", - "blockRuntimeId" : 6437 + "blockRuntimeId" : 6443 }, { "id" : "minecraft:polished_blackstone_bricks", - "blockRuntimeId" : 5997 + "blockRuntimeId" : 6003 }, { "id" : "minecraft:cracked_polished_blackstone_bricks", - "blockRuntimeId" : 3769 + "blockRuntimeId" : 3770 }, { "id" : "minecraft:gilded_blackstone", - "blockRuntimeId" : 4881 + "blockRuntimeId" : 4883 }, { "id" : "minecraft:chiseled_polished_blackstone", @@ -853,19 +853,19 @@ }, { "id" : "minecraft:deepslate_tiles", - "blockRuntimeId" : 4459 + "blockRuntimeId" : 4460 }, { "id" : "minecraft:cracked_deepslate_tiles", - "blockRuntimeId" : 3767 + "blockRuntimeId" : 3768 }, { "id" : "minecraft:deepslate_bricks", - "blockRuntimeId" : 4276 + "blockRuntimeId" : 4277 }, { "id" : "minecraft:cracked_deepslate_bricks", - "blockRuntimeId" : 3766 + "blockRuntimeId" : 3767 }, { "id" : "minecraft:chiseled_deepslate", @@ -873,195 +873,195 @@ }, { "id" : "minecraft:cobblestone", - "blockRuntimeId" : 1317 + "blockRuntimeId" : 1318 }, { "id" : "minecraft:mossy_cobblestone", - "blockRuntimeId" : 5666 + "blockRuntimeId" : 5668 }, { "id" : "minecraft:cobbled_deepslate", - "blockRuntimeId" : 1142 + "blockRuntimeId" : 1143 }, { "id" : "minecraft:smooth_stone", - "blockRuntimeId" : 6907 + "blockRuntimeId" : 6908 }, { "id" : "minecraft:sandstone", - "blockRuntimeId" : 6703 + "blockRuntimeId" : 6709 }, { "id" : "minecraft:sandstone", - "blockRuntimeId" : 6704 + "blockRuntimeId" : 6710 }, { "id" : "minecraft:sandstone", - "blockRuntimeId" : 6705 + "blockRuntimeId" : 6711 }, { "id" : "minecraft:sandstone", - "blockRuntimeId" : 6706 + "blockRuntimeId" : 6712 }, { "id" : "minecraft:red_sandstone", - "blockRuntimeId" : 6630 + "blockRuntimeId" : 6636 }, { "id" : "minecraft:red_sandstone", - "blockRuntimeId" : 6631 + "blockRuntimeId" : 6637 }, { "id" : "minecraft:red_sandstone", - "blockRuntimeId" : 6632 + "blockRuntimeId" : 6638 }, { "id" : "minecraft:red_sandstone", - "blockRuntimeId" : 6633 + "blockRuntimeId" : 6639 }, { "id" : "minecraft:coal_block", - "blockRuntimeId" : 1140 + "blockRuntimeId" : 1141 }, { "id" : "minecraft:dried_kelp_block", - "blockRuntimeId" : 4583 + "blockRuntimeId" : 4584 }, { "id" : "minecraft:gold_block", - "blockRuntimeId" : 4974 + "blockRuntimeId" : 4976 }, { "id" : "minecraft:iron_block", - "blockRuntimeId" : 5133 + "blockRuntimeId" : 5135 }, { "id" : "minecraft:copper_block", - "blockRuntimeId" : 3676 + "blockRuntimeId" : 3677 }, { "id" : "minecraft:exposed_copper", - "blockRuntimeId" : 4751 - }, - { - "id" : "minecraft:weathered_copper", - "blockRuntimeId" : 7737 - }, - { - "id" : "minecraft:oxidized_copper", - "blockRuntimeId" : 5751 - }, - { - "id" : "minecraft:waxed_copper", - "blockRuntimeId" : 7681 - }, - { - "id" : "minecraft:waxed_exposed_copper", - "blockRuntimeId" : 7695 - }, - { - "id" : "minecraft:waxed_weathered_copper", - "blockRuntimeId" : 7723 - }, - { - "id" : "minecraft:waxed_oxidized_copper", - "blockRuntimeId" : 7709 - }, - { - "id" : "minecraft:cut_copper", - "blockRuntimeId" : 3909 - }, - { - "id" : "minecraft:exposed_cut_copper", "blockRuntimeId" : 4752 }, + { + "id" : "minecraft:weathered_copper", + "blockRuntimeId" : 7739 + }, + { + "id" : "minecraft:oxidized_copper", + "blockRuntimeId" : 5756 + }, + { + "id" : "minecraft:waxed_copper", + "blockRuntimeId" : 7683 + }, + { + "id" : "minecraft:waxed_exposed_copper", + "blockRuntimeId" : 7697 + }, + { + "id" : "minecraft:waxed_weathered_copper", + "blockRuntimeId" : 7725 + }, + { + "id" : "minecraft:waxed_oxidized_copper", + "blockRuntimeId" : 7711 + }, + { + "id" : "minecraft:cut_copper", + "blockRuntimeId" : 3910 + }, + { + "id" : "minecraft:exposed_cut_copper", + "blockRuntimeId" : 4753 + }, { "id" : "minecraft:weathered_cut_copper", - "blockRuntimeId" : 7738 + "blockRuntimeId" : 7740 }, { "id" : "minecraft:oxidized_cut_copper", - "blockRuntimeId" : 5752 + "blockRuntimeId" : 5757 }, { "id" : "minecraft:waxed_cut_copper", - "blockRuntimeId" : 7682 + "blockRuntimeId" : 7684 }, { "id" : "minecraft:waxed_exposed_cut_copper", - "blockRuntimeId" : 7696 + "blockRuntimeId" : 7698 }, { "id" : "minecraft:waxed_weathered_cut_copper", - "blockRuntimeId" : 7724 + "blockRuntimeId" : 7726 }, { "id" : "minecraft:waxed_oxidized_cut_copper", - "blockRuntimeId" : 7710 + "blockRuntimeId" : 7712 }, { "id" : "minecraft:emerald_block", - "blockRuntimeId" : 4716 + "blockRuntimeId" : 4717 }, { "id" : "minecraft:diamond_block", - "blockRuntimeId" : 4473 + "blockRuntimeId" : 4474 }, { "id" : "minecraft:lapis_block", - "blockRuntimeId" : 5364 + "blockRuntimeId" : 5366 }, { "id" : "minecraft:raw_iron_block", - "blockRuntimeId" : 6576 + "blockRuntimeId" : 6582 }, { "id" : "minecraft:raw_copper_block", - "blockRuntimeId" : 6574 + "blockRuntimeId" : 6580 }, { "id" : "minecraft:raw_gold_block", - "blockRuntimeId" : 6575 + "blockRuntimeId" : 6581 }, { "id" : "minecraft:quartz_block", - "blockRuntimeId" : 6542 + "blockRuntimeId" : 6548 }, { "id" : "minecraft:quartz_block", - "blockRuntimeId" : 6544 + "blockRuntimeId" : 6550 }, { "id" : "minecraft:quartz_block", - "blockRuntimeId" : 6543 + "blockRuntimeId" : 6549 }, { "id" : "minecraft:quartz_block", - "blockRuntimeId" : 6545 + "blockRuntimeId" : 6551 }, { "id" : "minecraft:prismarine", - "blockRuntimeId" : 6435 + "blockRuntimeId" : 6441 }, { "id" : "minecraft:prismarine", - "blockRuntimeId" : 6436 + "blockRuntimeId" : 6442 }, { "id" : "minecraft:slime", - "blockRuntimeId" : 6860 + "blockRuntimeId" : 6861 }, { "id" : "minecraft:honey_block", - "blockRuntimeId" : 5111 + "blockRuntimeId" : 5113 }, { "id" : "minecraft:honeycomb_block", - "blockRuntimeId" : 5112 + "blockRuntimeId" : 5114 }, { "id" : "minecraft:hay_block", - "blockRuntimeId" : 5083 + "blockRuntimeId" : 5085 }, { "id" : "minecraft:bone_block", @@ -1069,27 +1069,51 @@ }, { "id" : "minecraft:nether_brick", - "blockRuntimeId" : 5685 + "blockRuntimeId" : 5689 }, { "id" : "minecraft:red_nether_brick", - "blockRuntimeId" : 6621 + "blockRuntimeId" : 6627 }, { "id" : "minecraft:netherite_block", - "blockRuntimeId" : 5702 + "blockRuntimeId" : 5706 }, { "id" : "minecraft:lodestone", - "blockRuntimeId" : 5562 + "blockRuntimeId" : 5564 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 7911 + "blockRuntimeId" : 7913 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 7919 + "blockRuntimeId" : 7921 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 7920 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 7928 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 7925 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 7927 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 7914 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 7917 }, { "id" : "minecraft:wool", @@ -1101,19 +1125,7 @@ }, { "id" : "minecraft:wool", - "blockRuntimeId" : 7923 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 7925 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 7912 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 7915 + "blockRuntimeId" : 7922 }, { "id" : "minecraft:wool", @@ -1125,27 +1137,15 @@ }, { "id" : "minecraft:wool", - "blockRuntimeId" : 7920 + "blockRuntimeId" : 7923 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 7914 + "blockRuntimeId" : 7915 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 7922 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 7921 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 7913 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 7917 + "blockRuntimeId" : 7919 }, { "id" : "minecraft:carpet", @@ -1211,93 +1211,69 @@ "id" : "minecraft:carpet", "blockRuntimeId" : 969 }, - { - "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3659 - }, - { - "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3667 - }, - { - "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3666 - }, - { - "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3674 - }, - { - "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3671 - }, - { - "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3673 - }, { "id" : "minecraft:concrete_powder", "blockRuntimeId" : 3660 }, - { - "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3663 - }, - { - "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3664 - }, - { - "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3672 - }, { "id" : "minecraft:concrete_powder", "blockRuntimeId" : 3668 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3662 + "blockRuntimeId" : 3667 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3670 + "blockRuntimeId" : 3675 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3669 + "blockRuntimeId" : 3672 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 3674 }, { "id" : "minecraft:concrete_powder", "blockRuntimeId" : 3661 }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 3664 + }, { "id" : "minecraft:concrete_powder", "blockRuntimeId" : 3665 }, { - "id" : "minecraft:concrete", - "blockRuntimeId" : 3643 + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 3673 }, { - "id" : "minecraft:concrete", - "blockRuntimeId" : 3651 + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 3669 }, { - "id" : "minecraft:concrete", - "blockRuntimeId" : 3650 + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 3663 }, { - "id" : "minecraft:concrete", - "blockRuntimeId" : 3658 + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 3671 }, { - "id" : "minecraft:concrete", - "blockRuntimeId" : 3655 + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 3670 }, { - "id" : "minecraft:concrete", - "blockRuntimeId" : 3657 + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 3662 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 3666 }, { "id" : "minecraft:concrete", @@ -1305,11 +1281,15 @@ }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 3647 + "blockRuntimeId" : 3652 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 3648 + "blockRuntimeId" : 3651 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 3659 }, { "id" : "minecraft:concrete", @@ -1317,59 +1297,55 @@ }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 3652 - }, - { - "id" : "minecraft:concrete", - "blockRuntimeId" : 3646 - }, - { - "id" : "minecraft:concrete", - "blockRuntimeId" : 3654 - }, - { - "id" : "minecraft:concrete", - "blockRuntimeId" : 3653 + "blockRuntimeId" : 3658 }, { "id" : "minecraft:concrete", "blockRuntimeId" : 3645 }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 3648 + }, { "id" : "minecraft:concrete", "blockRuntimeId" : 3649 }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 3657 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 3653 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 3647 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 3655 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 3654 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 3646 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 3650 + }, { "id" : "minecraft:clay", "blockRuntimeId" : 1139 }, { "id" : "minecraft:hardened_clay", - "blockRuntimeId" : 5082 - }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 7116 - }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 7124 - }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 7123 - }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 7131 - }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 7128 - }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 7130 + "blockRuntimeId" : 5084 }, { "id" : "minecraft:stained_hardened_clay", @@ -1377,11 +1353,15 @@ }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 7120 + "blockRuntimeId" : 7125 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 7121 + "blockRuntimeId" : 7124 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 7132 }, { "id" : "minecraft:stained_hardened_clay", @@ -1389,39 +1369,59 @@ }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 7125 - }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 7119 - }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 7127 - }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 7126 + "blockRuntimeId" : 7131 }, { "id" : "minecraft:stained_hardened_clay", "blockRuntimeId" : 7118 }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 7121 + }, { "id" : "minecraft:stained_hardened_clay", "blockRuntimeId" : 7122 }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 7130 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 7126 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 7120 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 7128 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 7127 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 7119 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 7123 + }, { "id" : "minecraft:white_glazed_terracotta", - "blockRuntimeId" : 7796 + "blockRuntimeId" : 7798 }, { "id" : "minecraft:silver_glazed_terracotta", - "blockRuntimeId" : 6842 + "blockRuntimeId" : 6849 }, { "id" : "minecraft:gray_glazed_terracotta", - "blockRuntimeId" : 5009 + "blockRuntimeId" : 5011 }, { "id" : "minecraft:black_glazed_terracotta", @@ -1433,31 +1433,31 @@ }, { "id" : "minecraft:red_glazed_terracotta", - "blockRuntimeId" : 6598 + "blockRuntimeId" : 6604 }, { "id" : "minecraft:orange_glazed_terracotta", - "blockRuntimeId" : 5745 + "blockRuntimeId" : 5750 }, { "id" : "minecraft:yellow_glazed_terracotta", - "blockRuntimeId" : 7938 + "blockRuntimeId" : 7940 }, { "id" : "minecraft:lime_glazed_terracotta", - "blockRuntimeId" : 5531 + "blockRuntimeId" : 5533 }, { "id" : "minecraft:green_glazed_terracotta", - "blockRuntimeId" : 5025 + "blockRuntimeId" : 5027 }, { "id" : "minecraft:cyan_glazed_terracotta", - "blockRuntimeId" : 3930 + "blockRuntimeId" : 3931 }, { "id" : "minecraft:light_blue_glazed_terracotta", - "blockRuntimeId" : 5483 + "blockRuntimeId" : 5485 }, { "id" : "minecraft:blue_glazed_terracotta", @@ -1465,43 +1465,43 @@ }, { "id" : "minecraft:purple_glazed_terracotta", - "blockRuntimeId" : 6516 - }, - { - "id" : "minecraft:magenta_glazed_terracotta", - "blockRuntimeId" : 5595 - }, - { - "id" : "minecraft:pink_glazed_terracotta", - "blockRuntimeId" : 5776 - }, - { - "id" : "minecraft:purpur_block", "blockRuntimeId" : 6522 }, + { + "id" : "minecraft:magenta_glazed_terracotta", + "blockRuntimeId" : 5597 + }, + { + "id" : "minecraft:pink_glazed_terracotta", + "blockRuntimeId" : 5782 + }, { "id" : "minecraft:purpur_block", - "blockRuntimeId" : 6524 + "blockRuntimeId" : 6528 + }, + { + "id" : "minecraft:purpur_block", + "blockRuntimeId" : 6530 }, { "id" : "minecraft:nether_wart_block", - "blockRuntimeId" : 5701 + "blockRuntimeId" : 5705 }, { "id" : "minecraft:warped_wart_block", - "blockRuntimeId" : 7663 + "blockRuntimeId" : 7665 }, { "id" : "minecraft:shroomlight", - "blockRuntimeId" : 6825 + "blockRuntimeId" : 6832 }, { "id" : "minecraft:crimson_nylium", - "blockRuntimeId" : 3838 + "blockRuntimeId" : 3839 }, { "id" : "minecraft:warped_nylium", - "blockRuntimeId" : 7593 + "blockRuntimeId" : 7595 }, { "id" : "minecraft:basalt", @@ -1509,87 +1509,87 @@ }, { "id" : "minecraft:polished_basalt", - "blockRuntimeId" : 5819 + "blockRuntimeId" : 5825 }, { "id" : "minecraft:smooth_basalt", - "blockRuntimeId" : 6882 + "blockRuntimeId" : 6883 }, { "id" : "minecraft:soul_soil", - "blockRuntimeId" : 6952 - }, - { - "id" : "minecraft:dirt", - "blockRuntimeId" : 4483 + "blockRuntimeId" : 6953 }, { "id" : "minecraft:dirt", "blockRuntimeId" : 4484 }, + { + "id" : "minecraft:dirt", + "blockRuntimeId" : 4485 + }, { "id" : "minecraft:farmland", - "blockRuntimeId" : 4765 + "blockRuntimeId" : 4766 }, { "id" : "minecraft:grass", - "blockRuntimeId" : 4996 + "blockRuntimeId" : 4998 }, { "id" : "minecraft:grass_path", - "blockRuntimeId" : 4997 + "blockRuntimeId" : 4999 }, { "id" : "minecraft:podzol", - "blockRuntimeId" : 5800 + "blockRuntimeId" : 5806 }, { "id" : "minecraft:mycelium", - "blockRuntimeId" : 5684 + "blockRuntimeId" : 5686 }, { "id" : "minecraft:stone", - "blockRuntimeId" : 7176 + "blockRuntimeId" : 7177 }, { "id" : "minecraft:iron_ore", - "blockRuntimeId" : 5166 + "blockRuntimeId" : 5168 }, { "id" : "minecraft:gold_ore", - "blockRuntimeId" : 4975 + "blockRuntimeId" : 4977 }, { "id" : "minecraft:diamond_ore", - "blockRuntimeId" : 4474 + "blockRuntimeId" : 4475 }, { "id" : "minecraft:lapis_ore", - "blockRuntimeId" : 5365 + "blockRuntimeId" : 5367 }, { "id" : "minecraft:redstone_ore", - "blockRuntimeId" : 6644 + "blockRuntimeId" : 6650 }, { "id" : "minecraft:coal_ore", - "blockRuntimeId" : 1141 + "blockRuntimeId" : 1142 }, { "id" : "minecraft:copper_ore", - "blockRuntimeId" : 3677 + "blockRuntimeId" : 3678 }, { "id" : "minecraft:emerald_ore", - "blockRuntimeId" : 4717 + "blockRuntimeId" : 4718 }, { "id" : "minecraft:quartz_ore", - "blockRuntimeId" : 6555 + "blockRuntimeId" : 6561 }, { "id" : "minecraft:nether_gold_ore", - "blockRuntimeId" : 5695 + "blockRuntimeId" : 5699 }, { "id" : "minecraft:ancient_debris", @@ -1597,59 +1597,39 @@ }, { "id" : "minecraft:deepslate_iron_ore", - "blockRuntimeId" : 4282 - }, - { - "id" : "minecraft:deepslate_gold_ore", - "blockRuntimeId" : 4281 - }, - { - "id" : "minecraft:deepslate_diamond_ore", - "blockRuntimeId" : 4279 - }, - { - "id" : "minecraft:deepslate_lapis_ore", "blockRuntimeId" : 4283 }, { - "id" : "minecraft:deepslate_redstone_ore", - "blockRuntimeId" : 4284 + "id" : "minecraft:deepslate_gold_ore", + "blockRuntimeId" : 4282 }, { - "id" : "minecraft:deepslate_emerald_ore", + "id" : "minecraft:deepslate_diamond_ore", "blockRuntimeId" : 4280 }, { - "id" : "minecraft:deepslate_coal_ore", - "blockRuntimeId" : 4277 + "id" : "minecraft:deepslate_lapis_ore", + "blockRuntimeId" : 4284 }, { - "id" : "minecraft:deepslate_copper_ore", + "id" : "minecraft:deepslate_redstone_ore", + "blockRuntimeId" : 4285 + }, + { + "id" : "minecraft:deepslate_emerald_ore", + "blockRuntimeId" : 4281 + }, + { + "id" : "minecraft:deepslate_coal_ore", "blockRuntimeId" : 4278 }, + { + "id" : "minecraft:deepslate_copper_ore", + "blockRuntimeId" : 4279 + }, { "id" : "minecraft:gravel", - "blockRuntimeId" : 4998 - }, - { - "id" : "minecraft:stone", - "blockRuntimeId" : 7177 - }, - { - "id" : "minecraft:stone", - "blockRuntimeId" : 7179 - }, - { - "id" : "minecraft:stone", - "blockRuntimeId" : 7181 - }, - { - "id" : "minecraft:blackstone", - "blockRuntimeId" : 494 - }, - { - "id" : "minecraft:deepslate", - "blockRuntimeId" : 4099 + "blockRuntimeId" : 5000 }, { "id" : "minecraft:stone", @@ -1663,105 +1643,109 @@ "id" : "minecraft:stone", "blockRuntimeId" : 7182 }, + { + "id" : "minecraft:blackstone", + "blockRuntimeId" : 494 + }, + { + "id" : "minecraft:deepslate", + "blockRuntimeId" : 4100 + }, + { + "id" : "minecraft:stone", + "blockRuntimeId" : 7179 + }, + { + "id" : "minecraft:stone", + "blockRuntimeId" : 7181 + }, + { + "id" : "minecraft:stone", + "blockRuntimeId" : 7183 + }, { "id" : "minecraft:polished_blackstone", - "blockRuntimeId" : 5822 + "blockRuntimeId" : 5828 }, { "id" : "minecraft:polished_deepslate", - "blockRuntimeId" : 6200 + "blockRuntimeId" : 6206 }, { "id" : "minecraft:sand", - "blockRuntimeId" : 6701 + "blockRuntimeId" : 6707 }, { "id" : "minecraft:sand", - "blockRuntimeId" : 6702 + "blockRuntimeId" : 6708 }, { "id" : "minecraft:cactus", "blockRuntimeId" : 920 }, - { - "id" : "minecraft:log", - "blockRuntimeId" : 5563 - }, - { - "id" : "minecraft:stripped_oak_log", - "blockRuntimeId" : 7315 - }, - { - "id" : "minecraft:log", - "blockRuntimeId" : 5564 - }, - { - "id" : "minecraft:stripped_spruce_log", - "blockRuntimeId" : 7318 - }, { "id" : "minecraft:log", "blockRuntimeId" : 5565 }, { - "id" : "minecraft:stripped_birch_log", - "blockRuntimeId" : 7300 + "id" : "minecraft:stripped_oak_log", + "blockRuntimeId" : 7316 }, { "id" : "minecraft:log", "blockRuntimeId" : 5566 }, + { + "id" : "minecraft:stripped_spruce_log", + "blockRuntimeId" : 7319 + }, + { + "id" : "minecraft:log", + "blockRuntimeId" : 5567 + }, + { + "id" : "minecraft:stripped_birch_log", + "blockRuntimeId" : 7301 + }, + { + "id" : "minecraft:log", + "blockRuntimeId" : 5568 + }, { "id" : "minecraft:stripped_jungle_log", - "blockRuntimeId" : 7312 + "blockRuntimeId" : 7313 }, { "id" : "minecraft:log2", - "blockRuntimeId" : 5575 + "blockRuntimeId" : 5577 }, { "id" : "minecraft:stripped_acacia_log", - "blockRuntimeId" : 7297 + "blockRuntimeId" : 7298 }, { "id" : "minecraft:log2", - "blockRuntimeId" : 5576 + "blockRuntimeId" : 5578 }, { "id" : "minecraft:stripped_dark_oak_log", - "blockRuntimeId" : 7309 + "blockRuntimeId" : 7310 }, { "id" : "minecraft:crimson_stem", - "blockRuntimeId" : 3883 + "blockRuntimeId" : 3884 }, { "id" : "minecraft:stripped_crimson_stem", - "blockRuntimeId" : 7306 + "blockRuntimeId" : 7307 }, { "id" : "minecraft:warped_stem", - "blockRuntimeId" : 7638 + "blockRuntimeId" : 7640 }, { "id" : "minecraft:stripped_warped_stem", - "blockRuntimeId" : 7324 - }, - { - "id" : "minecraft:wood", - "blockRuntimeId" : 7803 - }, - { - "id" : "minecraft:wood", - "blockRuntimeId" : 7809 - }, - { - "id" : "minecraft:wood", - "blockRuntimeId" : 7804 - }, - { - "id" : "minecraft:wood", - "blockRuntimeId" : 7810 + "blockRuntimeId" : 7325 }, { "id" : "minecraft:wood", @@ -1795,29 +1779,37 @@ "id" : "minecraft:wood", "blockRuntimeId" : 7814 }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 7809 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 7815 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 7810 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 7816 + }, { "id" : "minecraft:crimson_hyphae", - "blockRuntimeId" : 3835 + "blockRuntimeId" : 3836 }, { "id" : "minecraft:stripped_crimson_hyphae", - "blockRuntimeId" : 7303 + "blockRuntimeId" : 7304 }, { "id" : "minecraft:warped_hyphae", - "blockRuntimeId" : 7590 + "blockRuntimeId" : 7592 }, { "id" : "minecraft:stripped_warped_hyphae", - "blockRuntimeId" : 7321 - }, - { - "id" : "minecraft:leaves", - "blockRuntimeId" : 5409 - }, - { - "id" : "minecraft:leaves", - "blockRuntimeId" : 5410 + "blockRuntimeId" : 7322 }, { "id" : "minecraft:leaves", @@ -1828,12 +1820,20 @@ "blockRuntimeId" : 5412 }, { - "id" : "minecraft:leaves2", - "blockRuntimeId" : 5425 + "id" : "minecraft:leaves", + "blockRuntimeId" : 5413 + }, + { + "id" : "minecraft:leaves", + "blockRuntimeId" : 5414 }, { "id" : "minecraft:leaves2", - "blockRuntimeId" : 5426 + "blockRuntimeId" : 5427 + }, + { + "id" : "minecraft:leaves2", + "blockRuntimeId" : 5428 }, { "id" : "minecraft:azalea_leaves", @@ -1845,27 +1845,27 @@ }, { "id" : "minecraft:sapling", - "blockRuntimeId" : 6715 + "blockRuntimeId" : 6721 }, { "id" : "minecraft:sapling", - "blockRuntimeId" : 6716 + "blockRuntimeId" : 6722 }, { "id" : "minecraft:sapling", - "blockRuntimeId" : 6717 + "blockRuntimeId" : 6723 }, { "id" : "minecraft:sapling", - "blockRuntimeId" : 6718 + "blockRuntimeId" : 6724 }, { "id" : "minecraft:sapling", - "blockRuntimeId" : 6719 + "blockRuntimeId" : 6725 }, { "id" : "minecraft:sapling", - "blockRuntimeId" : 6720 + "blockRuntimeId" : 6726 }, { "id" : "minecraft:bee_nest", @@ -1912,7 +1912,7 @@ }, { "id" : "minecraft:melon_block", - "blockRuntimeId" : 5608 + "blockRuntimeId" : 5610 }, { "id" : "minecraft:melon_slice" @@ -1928,7 +1928,7 @@ }, { "id" : "minecraft:pumpkin", - "blockRuntimeId" : 6454 + "blockRuntimeId" : 6460 }, { "id" : "minecraft:carved_pumpkin", @@ -1936,11 +1936,19 @@ }, { "id" : "minecraft:lit_pumpkin", - "blockRuntimeId" : 5550 + "blockRuntimeId" : 5552 }, { "id" : "minecraft:honeycomb" }, + { + "id" : "minecraft:tallgrass", + "blockRuntimeId" : 7346 + }, + { + "id" : "minecraft:double_plant", + "blockRuntimeId" : 4504 + }, { "id" : "minecraft:tallgrass", "blockRuntimeId" : 7345 @@ -1949,17 +1957,17 @@ "id" : "minecraft:double_plant", "blockRuntimeId" : 4503 }, - { - "id" : "minecraft:tallgrass", - "blockRuntimeId" : 7344 - }, - { - "id" : "minecraft:double_plant", - "blockRuntimeId" : 4502 - }, { "id" : "minecraft:nether_sprouts" }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 3682 + }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 3680 + }, { "id" : "minecraft:coral", "blockRuntimeId" : 3681 @@ -1970,15 +1978,15 @@ }, { "id" : "minecraft:coral", - "blockRuntimeId" : 3680 + "blockRuntimeId" : 3683 }, { "id" : "minecraft:coral", - "blockRuntimeId" : 3678 + "blockRuntimeId" : 3687 }, { "id" : "minecraft:coral", - "blockRuntimeId" : 3682 + "blockRuntimeId" : 3685 }, { "id" : "minecraft:coral", @@ -1990,15 +1998,15 @@ }, { "id" : "minecraft:coral", - "blockRuntimeId" : 3685 + "blockRuntimeId" : 3688 }, { - "id" : "minecraft:coral", - "blockRuntimeId" : 3683 + "id" : "minecraft:coral_fan", + "blockRuntimeId" : 3702 }, { - "id" : "minecraft:coral", - "blockRuntimeId" : 3687 + "id" : "minecraft:coral_fan", + "blockRuntimeId" : 3700 }, { "id" : "minecraft:coral_fan", @@ -2010,15 +2018,15 @@ }, { "id" : "minecraft:coral_fan", - "blockRuntimeId" : 3700 + "blockRuntimeId" : 3703 }, { - "id" : "minecraft:coral_fan", - "blockRuntimeId" : 3698 + "id" : "minecraft:coral_fan_dead", + "blockRuntimeId" : 3712 }, { - "id" : "minecraft:coral_fan", - "blockRuntimeId" : 3702 + "id" : "minecraft:coral_fan_dead", + "blockRuntimeId" : 3710 }, { "id" : "minecraft:coral_fan_dead", @@ -2030,58 +2038,26 @@ }, { "id" : "minecraft:coral_fan_dead", - "blockRuntimeId" : 3710 - }, - { - "id" : "minecraft:coral_fan_dead", - "blockRuntimeId" : 3708 - }, - { - "id" : "minecraft:coral_fan_dead", - "blockRuntimeId" : 3712 + "blockRuntimeId" : 3713 }, { "id" : "minecraft:kelp" }, { "id" : "minecraft:seagrass", - "blockRuntimeId" : 6821 + "blockRuntimeId" : 6828 }, { "id" : "minecraft:crimson_roots", - "blockRuntimeId" : 3856 + "blockRuntimeId" : 3857 }, { "id" : "minecraft:warped_roots", - "blockRuntimeId" : 7611 + "blockRuntimeId" : 7613 }, { "id" : "minecraft:yellow_flower", - "blockRuntimeId" : 7937 - }, - { - "id" : "minecraft:red_flower", - "blockRuntimeId" : 6587 - }, - { - "id" : "minecraft:red_flower", - "blockRuntimeId" : 6588 - }, - { - "id" : "minecraft:red_flower", - "blockRuntimeId" : 6589 - }, - { - "id" : "minecraft:red_flower", - "blockRuntimeId" : 6590 - }, - { - "id" : "minecraft:red_flower", - "blockRuntimeId" : 6591 - }, - { - "id" : "minecraft:red_flower", - "blockRuntimeId" : 6592 + "blockRuntimeId" : 7939 }, { "id" : "minecraft:red_flower", @@ -2104,8 +2080,28 @@ "blockRuntimeId" : 6597 }, { - "id" : "minecraft:double_plant", - "blockRuntimeId" : 4500 + "id" : "minecraft:red_flower", + "blockRuntimeId" : 6598 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 6599 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 6600 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 6601 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 6602 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 6603 }, { "id" : "minecraft:double_plant", @@ -2113,15 +2109,19 @@ }, { "id" : "minecraft:double_plant", - "blockRuntimeId" : 4504 + "blockRuntimeId" : 4502 }, { "id" : "minecraft:double_plant", "blockRuntimeId" : 4505 }, + { + "id" : "minecraft:double_plant", + "blockRuntimeId" : 4506 + }, { "id" : "minecraft:wither_rose", - "blockRuntimeId" : 7802 + "blockRuntimeId" : 7804 }, { "id" : "minecraft:white_dye" @@ -2188,23 +2188,23 @@ }, { "id" : "minecraft:vine", - "blockRuntimeId" : 7498 + "blockRuntimeId" : 7500 }, { "id" : "minecraft:weeping_vines", - "blockRuntimeId" : 7752 + "blockRuntimeId" : 7754 }, { "id" : "minecraft:twisting_vines", - "blockRuntimeId" : 7426 + "blockRuntimeId" : 7427 }, { "id" : "minecraft:waterlily", - "blockRuntimeId" : 7680 + "blockRuntimeId" : 7682 }, { "id" : "minecraft:deadbush", - "blockRuntimeId" : 4098 + "blockRuntimeId" : 4099 }, { "id" : "minecraft:bamboo", @@ -2212,15 +2212,15 @@ }, { "id" : "minecraft:snow", - "blockRuntimeId" : 6908 + "blockRuntimeId" : 6909 }, { "id" : "minecraft:ice", - "blockRuntimeId" : 5125 + "blockRuntimeId" : 5127 }, { "id" : "minecraft:packed_ice", - "blockRuntimeId" : 5765 + "blockRuntimeId" : 5770 }, { "id" : "minecraft:blue_ice", @@ -2228,35 +2228,31 @@ }, { "id" : "minecraft:snow_layer", - "blockRuntimeId" : 6909 + "blockRuntimeId" : 6910 }, { "id" : "minecraft:pointed_dripstone", - "blockRuntimeId" : 5806 - }, - { - "id" : "minecraft:sculk_sensor", - "blockRuntimeId" : 6745 + "blockRuntimeId" : 5812 }, { "id" : "minecraft:dripstone_block", - "blockRuntimeId" : 4584 + "blockRuntimeId" : 4585 }, { "id" : "minecraft:moss_carpet", - "blockRuntimeId" : 5665 + "blockRuntimeId" : 5667 }, { "id" : "minecraft:moss_block", - "blockRuntimeId" : 5664 + "blockRuntimeId" : 5666 }, { "id" : "minecraft:dirt_with_roots", - "blockRuntimeId" : 4485 + "blockRuntimeId" : 4486 }, { "id" : "minecraft:hanging_roots", - "blockRuntimeId" : 5047 + "blockRuntimeId" : 5049 }, { "id" : "minecraft:big_dripleaf", @@ -2264,11 +2260,11 @@ }, { "id" : "minecraft:small_dripleaf_block", - "blockRuntimeId" : 6874 + "blockRuntimeId" : 6875 }, { "id" : "minecraft:spore_blossom", - "blockRuntimeId" : 6961 + "blockRuntimeId" : 6962 }, { "id" : "minecraft:azalea", @@ -2276,11 +2272,11 @@ }, { "id" : "minecraft:flowering_azalea", - "blockRuntimeId" : 4814 + "blockRuntimeId" : 4815 }, { "id" : "minecraft:glow_lichen", - "blockRuntimeId" : 4971 + "blockRuntimeId" : 4973 }, { "id" : "minecraft:amethyst_block", @@ -2292,23 +2288,23 @@ }, { "id" : "minecraft:amethyst_cluster", - "blockRuntimeId" : 137 + "blockRuntimeId" : 138 }, { "id" : "minecraft:large_amethyst_bud", - "blockRuntimeId" : 5366 + "blockRuntimeId" : 5369 }, { "id" : "minecraft:medium_amethyst_bud", - "blockRuntimeId" : 5602 + "blockRuntimeId" : 5605 }, { "id" : "minecraft:small_amethyst_bud", - "blockRuntimeId" : 6861 + "blockRuntimeId" : 6863 }, { "id" : "minecraft:tuff", - "blockRuntimeId" : 7413 + "blockRuntimeId" : 7414 }, { "id" : "minecraft:calcite", @@ -2347,15 +2343,15 @@ }, { "id" : "minecraft:red_mushroom", - "blockRuntimeId" : 6604 + "blockRuntimeId" : 6610 }, { "id" : "minecraft:crimson_fungus", - "blockRuntimeId" : 3834 + "blockRuntimeId" : 3835 }, { "id" : "minecraft:warped_fungus", - "blockRuntimeId" : 7589 + "blockRuntimeId" : 7591 }, { "id" : "minecraft:brown_mushroom_block", @@ -2363,7 +2359,7 @@ }, { "id" : "minecraft:red_mushroom_block", - "blockRuntimeId" : 6619 + "blockRuntimeId" : 6625 }, { "id" : "minecraft:brown_mushroom_block", @@ -2390,21 +2386,13 @@ }, { "id" : "minecraft:web", - "blockRuntimeId" : 7751 + "blockRuntimeId" : 7753 }, { "id" : "minecraft:spider_eye" }, { "id" : "minecraft:mob_spawner", - "blockRuntimeId" : 5657 - }, - { - "id" : "minecraft:monster_egg", - "blockRuntimeId" : 5658 - }, - { - "id" : "minecraft:monster_egg", "blockRuntimeId" : 5659 }, { @@ -2423,17 +2411,25 @@ "id" : "minecraft:monster_egg", "blockRuntimeId" : 5663 }, + { + "id" : "minecraft:monster_egg", + "blockRuntimeId" : 5664 + }, + { + "id" : "minecraft:monster_egg", + "blockRuntimeId" : 5665 + }, { "id" : "minecraft:infested_deepslate", - "blockRuntimeId" : 5126 + "blockRuntimeId" : 5128 }, { "id" : "minecraft:dragon_egg", - "blockRuntimeId" : 4582 + "blockRuntimeId" : 4583 }, { "id" : "minecraft:turtle_egg", - "blockRuntimeId" : 7414 + "blockRuntimeId" : 7415 }, { "id" : "minecraft:chicken_spawn_egg" @@ -2635,11 +2631,11 @@ }, { "id" : "minecraft:obsidian", - "blockRuntimeId" : 5734 + "blockRuntimeId" : 5738 }, { "id" : "minecraft:crying_obsidian", - "blockRuntimeId" : 3908 + "blockRuntimeId" : 3909 }, { "id" : "minecraft:bedrock", @@ -2647,22 +2643,22 @@ }, { "id" : "minecraft:soul_sand", - "blockRuntimeId" : 6951 + "blockRuntimeId" : 6952 }, { "id" : "minecraft:netherrack", - "blockRuntimeId" : 5703 + "blockRuntimeId" : 5707 }, { "id" : "minecraft:magma", - "blockRuntimeId" : 5601 + "blockRuntimeId" : 5603 }, { "id" : "minecraft:nether_wart" }, { "id" : "minecraft:end_stone", - "blockRuntimeId" : 4744 + "blockRuntimeId" : 4745 }, { "id" : "minecraft:chorus_flower", @@ -2678,17 +2674,13 @@ { "id" : "minecraft:popped_chorus_fruit" }, - { - "id" : "minecraft:sponge", - "blockRuntimeId" : 6959 - }, { "id" : "minecraft:sponge", "blockRuntimeId" : 6960 }, { - "id" : "minecraft:coral_block", - "blockRuntimeId" : 3688 + "id" : "minecraft:sponge", + "blockRuntimeId" : 6961 }, { "id" : "minecraft:coral_block", @@ -2726,6 +2718,10 @@ "id" : "minecraft:coral_block", "blockRuntimeId" : 3697 }, + { + "id" : "minecraft:coral_block", + "blockRuntimeId" : 3698 + }, { "id" : "minecraft:leather_helmet" }, @@ -3751,23 +3747,23 @@ }, { "id" : "minecraft:torch", - "blockRuntimeId" : 7353 + "blockRuntimeId" : 7354 }, { "id" : "minecraft:soul_torch", - "blockRuntimeId" : 6953 + "blockRuntimeId" : 6954 }, { "id" : "minecraft:sea_pickle", - "blockRuntimeId" : 6813 + "blockRuntimeId" : 6820 }, { "id" : "minecraft:lantern", - "blockRuntimeId" : 5362 + "blockRuntimeId" : 5364 }, { "id" : "minecraft:soul_lantern", - "blockRuntimeId" : 6949 + "blockRuntimeId" : 6950 }, { "id" : "minecraft:candle", @@ -3775,47 +3771,47 @@ }, { "id" : "minecraft:white_candle", - "blockRuntimeId" : 7786 + "blockRuntimeId" : 7788 }, { "id" : "minecraft:orange_candle", - "blockRuntimeId" : 5735 + "blockRuntimeId" : 5740 }, { "id" : "minecraft:magenta_candle", - "blockRuntimeId" : 5585 + "blockRuntimeId" : 5587 }, { "id" : "minecraft:light_blue_candle", - "blockRuntimeId" : 5473 + "blockRuntimeId" : 5475 }, { "id" : "minecraft:yellow_candle", - "blockRuntimeId" : 7927 + "blockRuntimeId" : 7929 }, { "id" : "minecraft:lime_candle", - "blockRuntimeId" : 5521 + "blockRuntimeId" : 5523 }, { "id" : "minecraft:pink_candle", - "blockRuntimeId" : 5766 + "blockRuntimeId" : 5772 }, { "id" : "minecraft:gray_candle", - "blockRuntimeId" : 4999 + "blockRuntimeId" : 5001 }, { "id" : "minecraft:light_gray_candle", - "blockRuntimeId" : 5489 + "blockRuntimeId" : 5491 }, { "id" : "minecraft:cyan_candle", - "blockRuntimeId" : 3920 + "blockRuntimeId" : 3921 }, { "id" : "minecraft:purple_candle", - "blockRuntimeId" : 6506 + "blockRuntimeId" : 6512 }, { "id" : "minecraft:blue_candle", @@ -3827,11 +3823,11 @@ }, { "id" : "minecraft:green_candle", - "blockRuntimeId" : 5015 + "blockRuntimeId" : 5017 }, { "id" : "minecraft:red_candle", - "blockRuntimeId" : 6577 + "blockRuntimeId" : 6583 }, { "id" : "minecraft:black_candle", @@ -3839,7 +3835,7 @@ }, { "id" : "minecraft:crafting_table", - "blockRuntimeId" : 3770 + "blockRuntimeId" : 3771 }, { "id" : "minecraft:cartography_table", @@ -3847,11 +3843,11 @@ }, { "id" : "minecraft:fletching_table", - "blockRuntimeId" : 4811 + "blockRuntimeId" : 4812 }, { "id" : "minecraft:smithing_table", - "blockRuntimeId" : 6875 + "blockRuntimeId" : 6876 }, { "id" : "minecraft:beehive", @@ -3865,7 +3861,7 @@ }, { "id" : "minecraft:furnace", - "blockRuntimeId" : 4875 + "blockRuntimeId" : 4877 }, { "id" : "minecraft:blast_furnace", @@ -3873,11 +3869,11 @@ }, { "id" : "minecraft:smoker", - "blockRuntimeId" : 6876 + "blockRuntimeId" : 6877 }, { "id" : "minecraft:respawn_anchor", - "blockRuntimeId" : 6696 + "blockRuntimeId" : 6702 }, { "id" : "minecraft:brewing_stand" @@ -3896,11 +3892,11 @@ }, { "id" : "minecraft:grindstone", - "blockRuntimeId" : 5031 + "blockRuntimeId" : 5033 }, { "id" : "minecraft:enchanting_table", - "blockRuntimeId" : 4718 + "blockRuntimeId" : 4719 }, { "id" : "minecraft:bookshelf", @@ -3908,14 +3904,14 @@ }, { "id" : "minecraft:lectern", - "blockRuntimeId" : 5433 + "blockRuntimeId" : 5435 }, { "id" : "minecraft:cauldron" }, { "id" : "minecraft:composter", - "blockRuntimeId" : 3634 + "blockRuntimeId" : 3635 }, { "id" : "minecraft:chest", @@ -3923,11 +3919,11 @@ }, { "id" : "minecraft:trapped_chest", - "blockRuntimeId" : 7375 + "blockRuntimeId" : 7376 }, { "id" : "minecraft:ender_chest", - "blockRuntimeId" : 4745 + "blockRuntimeId" : 4746 }, { "id" : "minecraft:barrel", @@ -3935,15 +3931,7 @@ }, { "id" : "minecraft:undyed_shulker_box", - "blockRuntimeId" : 7458 - }, - { - "id" : "minecraft:shulker_box", - "blockRuntimeId" : 6826 - }, - { - "id" : "minecraft:shulker_box", - "blockRuntimeId" : 6834 + "blockRuntimeId" : 7459 }, { "id" : "minecraft:shulker_box", @@ -3953,64 +3941,72 @@ "id" : "minecraft:shulker_box", "blockRuntimeId" : 6841 }, - { - "id" : "minecraft:shulker_box", - "blockRuntimeId" : 6838 - }, { "id" : "minecraft:shulker_box", "blockRuntimeId" : 6840 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 6827 + "blockRuntimeId" : 6848 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 6830 + "blockRuntimeId" : 6845 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 6831 + "blockRuntimeId" : 6847 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 6839 - }, - { - "id" : "minecraft:shulker_box", - "blockRuntimeId" : 6835 - }, - { - "id" : "minecraft:shulker_box", - "blockRuntimeId" : 6829 + "blockRuntimeId" : 6834 }, { "id" : "minecraft:shulker_box", "blockRuntimeId" : 6837 }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 6838 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 6846 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 6842 + }, { "id" : "minecraft:shulker_box", "blockRuntimeId" : 6836 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 6828 + "blockRuntimeId" : 6844 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 6832 + "blockRuntimeId" : 6843 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 6835 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 6839 }, { "id" : "minecraft:armor_stand" }, { "id" : "minecraft:noteblock", - "blockRuntimeId" : 5713 + "blockRuntimeId" : 5717 }, { "id" : "minecraft:jukebox", - "blockRuntimeId" : 5207 + "blockRuntimeId" : 5209 }, { "id" : "minecraft:music_disc_13" @@ -4048,6 +4044,9 @@ { "id" : "minecraft:music_disc_wait" }, + { + "id" : "minecraft:music_disc_otherside" + }, { "id" : "minecraft:music_disc_pigstep" }, @@ -4056,15 +4055,15 @@ }, { "id" : "minecraft:glowstone", - "blockRuntimeId" : 4973 + "blockRuntimeId" : 4975 }, { "id" : "minecraft:redstone_lamp", - "blockRuntimeId" : 6643 + "blockRuntimeId" : 6649 }, { "id" : "minecraft:sealantern", - "blockRuntimeId" : 6824 + "blockRuntimeId" : 6831 }, { "id" : "minecraft:oak_sign" @@ -4171,15 +4170,15 @@ }, { "id" : "minecraft:conduit", - "blockRuntimeId" : 3675 + "blockRuntimeId" : 3676 }, { "id" : "minecraft:stonecutter_block", - "blockRuntimeId" : 7291 + "blockRuntimeId" : 7292 }, { "id" : "minecraft:end_portal_frame", - "blockRuntimeId" : 4730 + "blockRuntimeId" : 4731 }, { "id" : "minecraft:coal" @@ -4315,11 +4314,11 @@ }, { "id" : "minecraft:end_rod", - "blockRuntimeId" : 4738 + "blockRuntimeId" : 4739 }, { "id" : "minecraft:lightning_rod", - "blockRuntimeId" : 5515 + "blockRuntimeId" : 5517 }, { "id" : "minecraft:end_crystal" @@ -4781,15 +4780,15 @@ }, { "id" : "minecraft:rail", - "blockRuntimeId" : 6564 + "blockRuntimeId" : 6570 }, { "id" : "minecraft:golden_rail", - "blockRuntimeId" : 4976 + "blockRuntimeId" : 4978 }, { "id" : "minecraft:detector_rail", - "blockRuntimeId" : 4461 + "blockRuntimeId" : 4462 }, { "id" : "minecraft:activator_rail", @@ -4812,23 +4811,23 @@ }, { "id" : "minecraft:redstone_block", - "blockRuntimeId" : 6642 + "blockRuntimeId" : 6648 }, { "id" : "minecraft:redstone_torch", - "blockRuntimeId" : 6645 + "blockRuntimeId" : 6651 }, { "id" : "minecraft:lever", - "blockRuntimeId" : 5441 + "blockRuntimeId" : 5443 }, { "id" : "minecraft:wooden_button", - "blockRuntimeId" : 7839 + "blockRuntimeId" : 7841 }, { "id" : "minecraft:spruce_button", - "blockRuntimeId" : 6962 + "blockRuntimeId" : 6963 }, { "id" : "minecraft:birch_button", @@ -4836,42 +4835,42 @@ }, { "id" : "minecraft:jungle_button", - "blockRuntimeId" : 5208 + "blockRuntimeId" : 5210 }, { "id" : "minecraft:acacia_button" }, { "id" : "minecraft:dark_oak_button", - "blockRuntimeId" : 3936 + "blockRuntimeId" : 3937 }, { "id" : "minecraft:stone_button", - "blockRuntimeId" : 7191 + "blockRuntimeId" : 7192 }, { "id" : "minecraft:crimson_button", - "blockRuntimeId" : 3771 + "blockRuntimeId" : 3772 }, { "id" : "minecraft:warped_button", - "blockRuntimeId" : 7526 + "blockRuntimeId" : 7528 }, { "id" : "minecraft:polished_blackstone_button", - "blockRuntimeId" : 5998 + "blockRuntimeId" : 6004 }, { "id" : "minecraft:tripwire_hook", - "blockRuntimeId" : 7397 + "blockRuntimeId" : 7398 }, { "id" : "minecraft:wooden_pressure_plate", - "blockRuntimeId" : 7883 + "blockRuntimeId" : 7885 }, { "id" : "minecraft:spruce_pressure_plate", - "blockRuntimeId" : 7022 + "blockRuntimeId" : 7023 }, { "id" : "minecraft:birch_pressure_plate", @@ -4879,7 +4878,7 @@ }, { "id" : "minecraft:jungle_pressure_plate", - "blockRuntimeId" : 5268 + "blockRuntimeId" : 5270 }, { "id" : "minecraft:acacia_pressure_plate", @@ -4887,39 +4886,39 @@ }, { "id" : "minecraft:dark_oak_pressure_plate", - "blockRuntimeId" : 3996 + "blockRuntimeId" : 3997 }, { "id" : "minecraft:crimson_pressure_plate", - "blockRuntimeId" : 3840 + "blockRuntimeId" : 3841 }, { "id" : "minecraft:warped_pressure_plate", - "blockRuntimeId" : 7595 + "blockRuntimeId" : 7597 }, { "id" : "minecraft:stone_pressure_plate", - "blockRuntimeId" : 7203 + "blockRuntimeId" : 7204 }, { "id" : "minecraft:light_weighted_pressure_plate", - "blockRuntimeId" : 5499 + "blockRuntimeId" : 5501 }, { "id" : "minecraft:heavy_weighted_pressure_plate", - "blockRuntimeId" : 5095 + "blockRuntimeId" : 5097 }, { "id" : "minecraft:polished_blackstone_pressure_plate", - "blockRuntimeId" : 6012 + "blockRuntimeId" : 6018 }, { "id" : "minecraft:observer", - "blockRuntimeId" : 5722 + "blockRuntimeId" : 5726 }, { "id" : "minecraft:daylight_detector", - "blockRuntimeId" : 4066 + "blockRuntimeId" : 4067 }, { "id" : "minecraft:repeater" @@ -4932,30 +4931,30 @@ }, { "id" : "minecraft:dropper", - "blockRuntimeId" : 4588 + "blockRuntimeId" : 4589 }, { "id" : "minecraft:dispenser", - "blockRuntimeId" : 4489 + "blockRuntimeId" : 4490 }, { "id" : "minecraft:piston", - "blockRuntimeId" : 5783 + "blockRuntimeId" : 5789 }, { "id" : "minecraft:sticky_piston", - "blockRuntimeId" : 7165 + "blockRuntimeId" : 7166 }, { "id" : "minecraft:tnt", - "blockRuntimeId" : 7349 + "blockRuntimeId" : 7350 }, { "id" : "minecraft:name_tag" }, { "id" : "minecraft:loom", - "blockRuntimeId" : 5581 + "blockRuntimeId" : 5583 }, { "id" : "minecraft:banner" @@ -5046,6 +5045,9 @@ { "id" : "minecraft:piglin_banner_pattern" }, + { + "id" : "minecraft:globe_banner_pattern" + }, { "id" : "minecraft:firework_rocket", "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwAAAAAAAQYARmxpZ2h0AQAA" @@ -5198,7 +5200,7 @@ }, { "id" : "minecraft:target", - "blockRuntimeId" : 7347 + "blockRuntimeId" : 7348 }, { "id" : "minecraft:lodestone_compass" diff --git a/core/src/main/resources/bedrock/runtime_item_states.1_17_30.json b/core/src/main/resources/bedrock/runtime_item_states.1_18_10.json similarity index 98% rename from core/src/main/resources/bedrock/runtime_item_states.1_17_30.json rename to core/src/main/resources/bedrock/runtime_item_states.1_18_10.json index 79690e3da..5bebcaf99 100644 --- a/core/src/main/resources/bedrock/runtime_item_states.1_17_30.json +++ b/core/src/main/resources/bedrock/runtime_item_states.1_18_10.json @@ -51,6 +51,10 @@ "name" : "minecraft:air", "id" : -158 }, + { + "name" : "minecraft:allay_spawn_egg", + "id" : 631 + }, { "name" : "minecraft:allow", "id" : 210 @@ -65,7 +69,7 @@ }, { "name" : "minecraft:amethyst_shard", - "id" : 623 + "id" : 625 }, { "name" : "minecraft:ancient_debris", @@ -117,7 +121,7 @@ }, { "name" : "minecraft:balloon", - "id" : 597 + "id" : 598 }, { "name" : "minecraft:bamboo", @@ -133,7 +137,7 @@ }, { "name" : "minecraft:banner_pattern", - "id" : 627 + "id" : 635 }, { "name" : "minecraft:barrel", @@ -293,7 +297,7 @@ }, { "name" : "minecraft:bleach", - "id" : 595 + "id" : 596 }, { "name" : "minecraft:blue_candle", @@ -317,7 +321,7 @@ }, { "name" : "minecraft:boat", - "id" : 625 + "id" : 633 }, { "name" : "minecraft:bone", @@ -429,11 +433,11 @@ }, { "name" : "minecraft:camera", - "id" : 592 + "id" : 593 }, { "name" : "minecraft:campfire", - "id" : 588 + "id" : 589 }, { "name" : "minecraft:candle", @@ -493,7 +497,7 @@ }, { "name" : "minecraft:chain", - "id" : 617 + "id" : 619 }, { "name" : "minecraft:chain_command_block", @@ -575,6 +579,10 @@ "name" : "minecraft:clay_ball", "id" : 384 }, + { + "name" : "minecraft:client_request_placeholder_block", + "id" : -465 + }, { "name" : "minecraft:clock", "id" : 393 @@ -669,7 +677,7 @@ }, { "name" : "minecraft:compound", - "id" : 593 + "id" : 594 }, { "name" : "minecraft:concrete", @@ -793,7 +801,7 @@ }, { "name" : "minecraft:crimson_door", - "id" : 614 + "id" : 616 }, { "name" : "minecraft:crimson_double_slab", @@ -833,7 +841,7 @@ }, { "name" : "minecraft:crimson_sign", - "id" : 612 + "id" : 614 }, { "name" : "minecraft:crimson_slab", @@ -1169,7 +1177,7 @@ }, { "name" : "minecraft:dye", - "id" : 626 + "id" : 634 }, { "name" : "minecraft:egg", @@ -1697,7 +1705,7 @@ }, { "name" : "minecraft:end_crystal", - "id" : 629 + "id" : 637 }, { "name" : "minecraft:end_gateway", @@ -1803,6 +1811,10 @@ "name" : "minecraft:fire_charge", "id" : 509 }, + { + "name" : "minecraft:firefly_spawn_egg", + "id" : 632 + }, { "name" : "minecraft:firework_rocket", "id" : 519 @@ -1855,6 +1867,14 @@ "name" : "minecraft:frame", "id" : 513 }, + { + "name" : "minecraft:frog_egg", + "id" : -468 + }, + { + "name" : "minecraft:frog_spawn_egg", + "id" : 628 + }, { "name" : "minecraft:frosted_ice", "id" : 207 @@ -1891,13 +1911,17 @@ "name" : "minecraft:glistering_melon_slice", "id" : 434 }, + { + "name" : "minecraft:globe_banner_pattern", + "id" : 588 + }, { "name" : "minecraft:glow_berries", - "id" : 630 + "id" : 638 }, { "name" : "minecraft:glow_frame", - "id" : 621 + "id" : 623 }, { "name" : "minecraft:glow_ink_sac", @@ -1913,7 +1937,7 @@ }, { "name" : "minecraft:glow_stick", - "id" : 166 + "id" : 601 }, { "name" : "minecraft:glowingobsidian", @@ -1929,7 +1953,7 @@ }, { "name" : "minecraft:goat_horn", - "id" : 622 + "id" : 624 }, { "name" : "minecraft:goat_spawn_egg", @@ -2109,11 +2133,11 @@ }, { "name" : "minecraft:honey_bottle", - "id" : 591 + "id" : 592 }, { "name" : "minecraft:honeycomb", - "id" : 590 + "id" : 591 }, { "name" : "minecraft:honeycomb_block", @@ -2141,7 +2165,7 @@ }, { "name" : "minecraft:ice_bomb", - "id" : 594 + "id" : 595 }, { "name" : "minecraft:infested_deepslate", @@ -2569,7 +2593,7 @@ }, { "name" : "minecraft:lodestone_compass", - "id" : 600 + "id" : 602 }, { "name" : "minecraft:log", @@ -2613,7 +2637,7 @@ }, { "name" : "minecraft:medicine", - "id" : 598 + "id" : 599 }, { "name" : "minecraft:medium_amethyst_bud", @@ -2723,9 +2747,13 @@ "name" : "minecraft:music_disc_mellohi", "id" : 540 }, + { + "name" : "minecraft:music_disc_otherside", + "id" : 627 + }, { "name" : "minecraft:music_disc_pigstep", - "id" : 618 + "id" : 620 }, { "name" : "minecraft:music_disc_stal", @@ -2751,6 +2779,14 @@ "name" : "minecraft:mycelium", "id" : 110 }, + { + "name" : "minecraft:mysterious_frame", + "id" : -466 + }, + { + "name" : "minecraft:mysterious_frame_slot", + "id" : -467 + }, { "name" : "minecraft:name_tag", "id" : 548 @@ -2777,7 +2813,7 @@ }, { "name" : "minecraft:nether_sprouts", - "id" : 619 + "id" : 621 }, { "name" : "minecraft:nether_star", @@ -2797,7 +2833,7 @@ }, { "name" : "minecraft:netherite_axe", - "id" : 605 + "id" : 607 }, { "name" : "minecraft:netherite_block", @@ -2805,43 +2841,43 @@ }, { "name" : "minecraft:netherite_boots", - "id" : 610 + "id" : 612 }, { "name" : "minecraft:netherite_chestplate", - "id" : 608 + "id" : 610 }, { "name" : "minecraft:netherite_helmet", - "id" : 607 - }, - { - "name" : "minecraft:netherite_hoe", - "id" : 606 - }, - { - "name" : "minecraft:netherite_ingot", - "id" : 601 - }, - { - "name" : "minecraft:netherite_leggings", "id" : 609 }, { - "name" : "minecraft:netherite_pickaxe", - "id" : 604 + "name" : "minecraft:netherite_hoe", + "id" : 608 }, { - "name" : "minecraft:netherite_scrap", - "id" : 611 - }, - { - "name" : "minecraft:netherite_shovel", + "name" : "minecraft:netherite_ingot", "id" : 603 }, + { + "name" : "minecraft:netherite_leggings", + "id" : 611 + }, + { + "name" : "minecraft:netherite_pickaxe", + "id" : 606 + }, + { + "name" : "minecraft:netherite_scrap", + "id" : 613 + }, + { + "name" : "minecraft:netherite_shovel", + "id" : 605 + }, { "name" : "minecraft:netherite_sword", - "id" : 602 + "id" : 604 }, { "name" : "minecraft:netherrack", @@ -2887,6 +2923,10 @@ "name" : "minecraft:ocelot_spawn_egg", "id" : 451 }, + { + "name" : "minecraft:ochre_froglight", + "id" : -471 + }, { "name" : "minecraft:orange_candle", "id" : -414 @@ -2943,6 +2983,10 @@ "name" : "minecraft:parrot_spawn_egg", "id" : 478 }, + { + "name" : "minecraft:pearlescent_froglight", + "id" : -469 + }, { "name" : "minecraft:phantom_membrane", "id" : 574 @@ -3257,7 +3301,7 @@ }, { "name" : "minecraft:rapid_fertilizer", - "id" : 596 + "id" : 597 }, { "name" : "minecraft:ravager_spawn_egg", @@ -3577,7 +3621,7 @@ }, { "name" : "minecraft:soul_campfire", - "id" : 620 + "id" : 622 }, { "name" : "minecraft:soul_fire", @@ -3601,11 +3645,11 @@ }, { "name" : "minecraft:sparkler", - "id" : 599 + "id" : 600 }, { "name" : "minecraft:spawn_egg", - "id" : 628 + "id" : 636 }, { "name" : "minecraft:spider_eye", @@ -3669,7 +3713,7 @@ }, { "name" : "minecraft:spyglass", - "id" : 624 + "id" : 626 }, { "name" : "minecraft:squid_spawn_egg", @@ -3829,7 +3873,7 @@ }, { "name" : "minecraft:suspicious_stew", - "id" : 589 + "id" : 590 }, { "name" : "minecraft:sweet_berries", @@ -3839,6 +3883,14 @@ "name" : "minecraft:sweet_berry_bush", "id" : -207 }, + { + "name" : "minecraft:tadpole_bucket", + "id" : 630 + }, + { + "name" : "minecraft:tadpole_spawn_egg", + "id" : 629 + }, { "name" : "minecraft:tallgrass", "id" : 31 @@ -3943,6 +3995,10 @@ "name" : "minecraft:unpowered_repeater", "id" : 93 }, + { + "name" : "minecraft:verdant_froglight", + "id" : -470 + }, { "name" : "minecraft:vex_spawn_egg", "id" : 476 @@ -3977,7 +4033,7 @@ }, { "name" : "minecraft:warped_door", - "id" : 615 + "id" : 617 }, { "name" : "minecraft:warped_double_slab", @@ -3997,7 +4053,7 @@ }, { "name" : "minecraft:warped_fungus_on_a_stick", - "id" : 616 + "id" : 618 }, { "name" : "minecraft:warped_hyphae", @@ -4021,7 +4077,7 @@ }, { "name" : "minecraft:warped_sign", - "id" : 613 + "id" : 615 }, { "name" : "minecraft:warped_slab", From e73a4efe606c4531769f4c045bb6cc20ef061550 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 7 Feb 2022 12:38:28 -0500 Subject: [PATCH 018/110] Fix build; bump to 2.0.1-SNAPSHOT --- ap/pom.xml | 4 ++-- api/base/pom.xml | 2 +- api/geyser/pom.xml | 4 ++-- api/pom.xml | 2 +- bootstrap/bungeecord/pom.xml | 4 ++-- bootstrap/pom.xml | 4 ++-- bootstrap/spigot/pom.xml | 4 ++-- bootstrap/sponge/pom.xml | 4 ++-- bootstrap/standalone/pom.xml | 4 ++-- bootstrap/velocity/pom.xml | 4 ++-- common/pom.xml | 2 +- core/pom.xml | 10 +++++----- pom.xml | 2 +- 13 files changed, 25 insertions(+), 25 deletions(-) diff --git a/ap/pom.xml b/ap/pom.xml index cc282dd55..dce28a7d7 100644 --- a/ap/pom.xml +++ b/ap/pom.xml @@ -6,9 +6,9 @@ org.geysermc geyser-parent - 2.0.0-SNAPSHOT + 2.0.1-SNAPSHOT ap - 2.0.0-SNAPSHOT + 2.0.1-SNAPSHOT \ No newline at end of file diff --git a/api/base/pom.xml b/api/base/pom.xml index 0d7ed05da..17edb1a85 100644 --- a/api/base/pom.xml +++ b/api/base/pom.xml @@ -5,7 +5,7 @@ org.geysermc api-parent - 2.0.0-SNAPSHOT + 2.0.1-SNAPSHOT 4.0.0 diff --git a/api/geyser/pom.xml b/api/geyser/pom.xml index 89349e8ac..26f4ddf30 100644 --- a/api/geyser/pom.xml +++ b/api/geyser/pom.xml @@ -5,7 +5,7 @@ org.geysermc api-parent - 2.0.0-SNAPSHOT + 2.0.1-SNAPSHOT 4.0.0 @@ -26,7 +26,7 @@ org.geysermc base-api - 2.0.0-SNAPSHOT + 2.0.1-SNAPSHOT compile diff --git a/api/pom.xml b/api/pom.xml index b3d0262ea..bc70be6c3 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -6,7 +6,7 @@ org.geysermc geyser-parent - 2.0.0-SNAPSHOT + 2.0.1-SNAPSHOT api-parent diff --git a/bootstrap/bungeecord/pom.xml b/bootstrap/bungeecord/pom.xml index 9dcd0943e..45a08c7db 100644 --- a/bootstrap/bungeecord/pom.xml +++ b/bootstrap/bungeecord/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 2.0.0-SNAPSHOT + 2.0.1-SNAPSHOT bootstrap-bungeecord @@ -14,7 +14,7 @@ org.geysermc core - 2.0.0-SNAPSHOT + 2.0.1-SNAPSHOT compile diff --git a/bootstrap/pom.xml b/bootstrap/pom.xml index 3b0bdda55..58c651455 100644 --- a/bootstrap/pom.xml +++ b/bootstrap/pom.xml @@ -6,7 +6,7 @@ org.geysermc geyser-parent - 2.0.0-SNAPSHOT + 2.0.1-SNAPSHOT bootstrap-parent pom @@ -34,7 +34,7 @@ org.geysermc ap - 2.0.0-SNAPSHOT + 2.0.1-SNAPSHOT provided diff --git a/bootstrap/spigot/pom.xml b/bootstrap/spigot/pom.xml index 5aa2c59cf..6eda527f3 100644 --- a/bootstrap/spigot/pom.xml +++ b/bootstrap/spigot/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 2.0.0-SNAPSHOT + 2.0.1-SNAPSHOT bootstrap-spigot @@ -25,7 +25,7 @@ org.geysermc core - 2.0.0-SNAPSHOT + 2.0.1-SNAPSHOT compile diff --git a/bootstrap/sponge/pom.xml b/bootstrap/sponge/pom.xml index fa7989b43..ab3b7d970 100644 --- a/bootstrap/sponge/pom.xml +++ b/bootstrap/sponge/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 2.0.0-SNAPSHOT + 2.0.1-SNAPSHOT bootstrap-sponge @@ -14,7 +14,7 @@ org.geysermc core - 2.0.0-SNAPSHOT + 2.0.1-SNAPSHOT compile diff --git a/bootstrap/standalone/pom.xml b/bootstrap/standalone/pom.xml index 00c0410e4..881c87e6c 100644 --- a/bootstrap/standalone/pom.xml +++ b/bootstrap/standalone/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 2.0.0-SNAPSHOT + 2.0.1-SNAPSHOT bootstrap-standalone @@ -18,7 +18,7 @@ org.geysermc core - 2.0.0-SNAPSHOT + 2.0.1-SNAPSHOT compile diff --git a/bootstrap/velocity/pom.xml b/bootstrap/velocity/pom.xml index e1e3331ef..ff052471d 100644 --- a/bootstrap/velocity/pom.xml +++ b/bootstrap/velocity/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 2.0.0-SNAPSHOT + 2.0.1-SNAPSHOT bootstrap-velocity @@ -14,7 +14,7 @@ org.geysermc core - 2.0.0-SNAPSHOT + 2.0.1-SNAPSHOT compile diff --git a/common/pom.xml b/common/pom.xml index 8e7be26f4..fde2605bc 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -6,7 +6,7 @@ org.geysermc geyser-parent - 2.0.0-SNAPSHOT + 2.0.1-SNAPSHOT common diff --git a/core/pom.xml b/core/pom.xml index c2e8b5f5b..f2812fea0 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -6,7 +6,7 @@ org.geysermc geyser-parent - 2.0.0-SNAPSHOT + 2.0.1-SNAPSHOT core @@ -21,19 +21,19 @@ org.geysermc ap - 2.0.0-SNAPSHOT + 2.0.1-SNAPSHOT provided org.geysermc geyser-api - 2.0.0-SNAPSHOT + 2.0.1-SNAPSHOT compile org.geysermc common - 2.0.0-SNAPSHOT + 2.0.1-SNAPSHOT compile @@ -121,7 +121,7 @@ com.github.CloudburstMC.Protocol bedrock-v486 - v1.18.10-c2c5a7069f-1 + 0cd24c0 compile diff --git a/pom.xml b/pom.xml index 004d58666..a528dcf98 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 org.geysermc geyser-parent - 2.0.0-SNAPSHOT + 2.0.1-SNAPSHOT pom Geyser Allows for players from Minecraft Bedrock Edition to join Minecraft Java Edition servers. From b0e91275dc517e41ee2d793e12fc40193da96481 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Tue, 8 Feb 2022 19:02:02 +0100 Subject: [PATCH 019/110] Updated the supported versions --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d4b375a5c..593514e52 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have joined us here! -### Currently supporting Minecraft Bedrock 1.17.30 - 1.17.41 + 1.18.0 - 1.18.2 and Minecraft Java 1.18/1.18.1. +### Currently supporting Minecraft Bedrock 1.17.41 + 1.18.0 - 1.18.10 and Minecraft Java 1.18/1.18.1. ## Setting Up Take a look [here](https://github.com/GeyserMC/Geyser/wiki/Setup) for how to set up Geyser. From 0eed6025a6cea3e5a197976ed8f2b68120c2918a Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 8 Feb 2022 22:57:03 -0500 Subject: [PATCH 020/110] Remove protocol checks referencing 1.17.30 These are now unnecessary. --- .../geyser/entity/type/ItemFrameEntity.java | 9 +++------ .../java/level/JavaLevelEventTranslator.java | 19 +++++-------------- 2 files changed, 8 insertions(+), 20 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/ItemFrameEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/ItemFrameEntity.java index 9139e0a99..69aac5a26 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/ItemFrameEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/ItemFrameEntity.java @@ -37,12 +37,11 @@ import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket; import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; -import com.nukkitx.protocol.bedrock.v465.Bedrock_v465; import lombok.Getter; import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.inventory.item.ItemTranslator; -import org.geysermc.geyser.registry.type.ItemMapping; import java.util.UUID; @@ -85,10 +84,8 @@ public class ItemFrameEntity extends Entity { .putInt("version", session.getBlockMappings().getBlockStateVersion()); NbtMapBuilder statesBuilder = NbtMap.builder() .putInt("facing_direction", direction.ordinal()) - .putByte("item_frame_map_bit", (byte) 0); - if (session.getUpstream().getProtocolVersion() >= Bedrock_v465.V465_CODEC.getProtocolVersion()) { - statesBuilder.putByte("item_frame_photo_bit", (byte) 0); - } + .putByte("item_frame_map_bit", (byte) 0) + .putByte("item_frame_photo_bit", (byte) 0); blockBuilder.put("states", statesBuilder.build()); bedrockRuntimeId = session.getBlockMappings().getItemFrame(blockBuilder.build()); diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelEventTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelEventTranslator.java index b24c7a363..2271388c2 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelEventTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelEventTranslator.java @@ -25,14 +25,7 @@ package org.geysermc.geyser.translator.protocol.java.level; -import com.github.steveice10.mc.protocol.data.game.level.event.BonemealGrowEventData; -import com.github.steveice10.mc.protocol.data.game.level.event.BreakBlockEventData; -import com.github.steveice10.mc.protocol.data.game.level.event.BreakPotionEventData; -import com.github.steveice10.mc.protocol.data.game.level.event.ComposterEventData; -import com.github.steveice10.mc.protocol.data.game.level.event.DragonFireballEventData; -import com.github.steveice10.mc.protocol.data.game.level.event.ParticleEvent; -import com.github.steveice10.mc.protocol.data.game.level.event.RecordEventData; -import com.github.steveice10.mc.protocol.data.game.level.event.SmokeEventData; +import com.github.steveice10.mc.protocol.data.game.level.event.*; import com.github.steveice10.mc.protocol.packet.ingame.clientbound.level.ClientboundLevelEventPacket; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.LevelEventType; @@ -40,14 +33,13 @@ import com.nukkitx.protocol.bedrock.data.SoundEvent; import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; import com.nukkitx.protocol.bedrock.packet.LevelSoundEventPacket; import com.nukkitx.protocol.bedrock.packet.TextPacket; -import com.nukkitx.protocol.bedrock.v465.Bedrock_v465; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.MinecraftLocale; +import org.geysermc.geyser.translator.level.event.LevelEventTranslator; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; -import org.geysermc.geyser.translator.level.event.LevelEventTranslator; -import org.geysermc.geyser.registry.Registries; -import org.geysermc.geyser.text.MinecraftLocale; import java.util.Collections; import java.util.Locale; @@ -218,8 +210,7 @@ public class JavaLevelEventTranslator extends PacketTranslator effectPacket.setType(LevelEventType.PARTICLE_EYE_OF_ENDER_DEATH); case MOB_SPAWN -> effectPacket.setType(LevelEventType.PARTICLE_MOB_BLOCK_SPAWN); // TODO: Check, but I don't think I really verified this ever went into effect on Java case BONEMEAL_GROW_WITH_SOUND, BONEMEAL_GROW -> { - effectPacket.setType((particleEvent == ParticleEvent.BONEMEAL_GROW - && session.getUpstream().getProtocolVersion() >= Bedrock_v465.V465_CODEC.getProtocolVersion()) ? LevelEventType.PARTICLE_TURTLE_EGG : LevelEventType.PARTICLE_CROP_GROWTH); + effectPacket.setType(particleEvent == ParticleEvent.BONEMEAL_GROW ? LevelEventType.PARTICLE_TURTLE_EGG : LevelEventType.PARTICLE_CROP_GROWTH); BonemealGrowEventData growEventData = (BonemealGrowEventData) packet.getData(); effectPacket.setData(growEventData.getParticleCount()); From 0479af7cd3ef13edc632c3b88ab5ddee94326d6b Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 8 Feb 2022 23:15:41 -0500 Subject: [PATCH 021/110] Restore ClickPlan double-simulation This is used in some autocrafting situations. Oops. --- .../geysermc/geyser/inventory/click/ClickPlan.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/geysermc/geyser/inventory/click/ClickPlan.java b/core/src/main/java/org/geysermc/geyser/inventory/click/ClickPlan.java index 45d535167..b0cca53d9 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/click/ClickPlan.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/click/ClickPlan.java @@ -75,6 +75,11 @@ public final class ClickPlan { gridSize = translator.getGridSize(); } + private void resetSimulation() { + this.simulatedItems.clear(); + this.simulatedCursor = session.getPlayerInventory().getCursor().copy(); + } + public void add(Click click, int slot) { add(click, slot, false); } @@ -89,10 +94,14 @@ public final class ClickPlan { ClickAction action = new ClickAction(click, slot, force); plan.add(action); + // RUNNING THE SIMULATION HERE IS IMPORTANT. The contents of the simulation are used in complex, multi-stage tasks + // such as autocrafting. + simulateAction(action); } public void execute(boolean refresh) { //update geyser inventory after simulation to avoid net id desync + resetSimulation(); ListIterator planIter = plan.listIterator(); while (planIter.hasNext()) { ClickAction action = planIter.next(); @@ -190,7 +199,9 @@ public final class ClickPlan { * Does not need to be called for the cursor */ private void onSlotItemChange(int slot, GeyserItemStack itemStack) { - changedItems.put(slot, itemStack.getItemStack()); + if (changedItems != null) { + changedItems.put(slot, itemStack.getItemStack()); + } } private void simulateAction(ClickAction action) { From 559c5d655ad2570fe6f2bdd8a392928db2f94a25 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 9 Feb 2022 15:08:58 -0500 Subject: [PATCH 022/110] Allow smithing recipes to work again in 1.18.10 --- .../java/JavaUpdateRecipesTranslator.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaUpdateRecipesTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaUpdateRecipesTranslator.java index 7a346dce1..c3c8abfb4 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaUpdateRecipesTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaUpdateRecipesTranslator.java @@ -31,12 +31,14 @@ import com.github.steveice10.mc.protocol.data.game.recipe.Recipe; import com.github.steveice10.mc.protocol.data.game.recipe.RecipeType; import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapedRecipeData; import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapelessRecipeData; +import com.github.steveice10.mc.protocol.data.game.recipe.data.SmithingRecipeData; import com.github.steveice10.mc.protocol.data.game.recipe.data.StoneCuttingRecipeData; import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundUpdateRecipesPacket; import com.nukkitx.nbt.NbtMap; import com.nukkitx.protocol.bedrock.data.inventory.CraftingData; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.packet.CraftingDataPacket; +import com.nukkitx.protocol.bedrock.v486.Bedrock_v486; import it.unimi.dsi.fastutil.ints.*; import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; @@ -76,6 +78,8 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator= Bedrock_v486.V486_CODEC.getProtocolVersion(); + Int2ObjectMap recipeMap = new Int2ObjectOpenHashMap<>(Registries.RECIPES.forVersion(session.getUpstream().getProtocolVersion())); Int2ObjectMap> unsortedStonecutterData = new Int2ObjectOpenHashMap<>(); CraftingDataPacket craftingDataPacket = new CraftingDataPacket(); @@ -128,6 +132,27 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator { + // Required to translate these as of 1.18.10, or else they cannot be crafted + if (!applySmithingRecipes) { + continue; + } + + SmithingRecipeData recipeData = (SmithingRecipeData) recipe.getData(); + ItemData output = ItemTranslator.translateToBedrock(session, recipeData.getResult()); + for (ItemStack base : recipeData.getBase().getOptions()) { + ItemData bedrockBase = ItemTranslator.translateToBedrock(session, base); + + for (ItemStack addition : recipeData.getAddition().getOptions()) { + ItemData bedrockAddition = ItemTranslator.translateToBedrock(session, addition); + + UUID uuid = UUID.randomUUID(); + craftingDataPacket.getCraftingData().add(CraftingData.fromShapeless(uuid.toString(), + Arrays.asList(bedrockBase, bedrockAddition), + Collections.singletonList(output), uuid, "smithing_table", 2, netId++)); + } + } + } default -> { List craftingData = recipeTypes.get(recipe.getType()); if (craftingData != null) { From 9ea59d616e2dea9113693df872aff83812430250 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Thu, 10 Feb 2022 18:52:14 +0000 Subject: [PATCH 023/110] Add sonarcloud analysis --- .github/workflows/sonarcloud.yml | 38 ++++++++++++++++++++++++++++++++ bootstrap/standalone/pom.xml | 1 - pom.xml | 2 ++ 3 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/sonarcloud.yml diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml new file mode 100644 index 000000000..64ac19621 --- /dev/null +++ b/.github/workflows/sonarcloud.yml @@ -0,0 +1,38 @@ +name: SonarCloud +on: + push: + branches: + - master + pull_request: + types: [opened, synchronize, reopened] +jobs: + build: + name: SonarCloud + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis + submodules: true + - name: Set up JDK 17 + uses: actions/setup-java@v2 + with: + distribution: 'temurin' + java-version: 17 + - name: Cache SonarCloud packages + uses: actions/cache@v1 + with: + path: ~/.sonar/cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + - name: Cache Maven packages + uses: actions/cache@v1 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2 + - name: Build and analyze + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + run: mvn -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=GeyserMC_Geyser \ No newline at end of file diff --git a/bootstrap/standalone/pom.xml b/bootstrap/standalone/pom.xml index 881c87e6c..0042a40d8 100644 --- a/bootstrap/standalone/pom.xml +++ b/bootstrap/standalone/pom.xml @@ -132,7 +132,6 @@ implementation="com.github.edwgiz.mavenShadePlugin.log4j2CacheTransformer.PluginsCacheFileTransformer"> - ${project.build.directory}/dependency-reduced-pom.xml diff --git a/pom.xml b/pom.xml index a528dcf98..f00da7fc8 100644 --- a/pom.xml +++ b/pom.xml @@ -17,6 +17,8 @@ UTF-8 16 16 + geysermc + https://sonarcloud.io From 746cd94dd1c2e92ae8e12a6cd39b4d0790f28a2d Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 10 Feb 2022 20:14:52 -0500 Subject: [PATCH 024/110] Fix villagers for 1.18.10 Includes working around pre-1.14 ONLY on pre-1.14 by checking the tags packet. Fixes #2828 --- .../geyser/inventory/MerchantContainer.java | 16 ++++++- .../geyser/session/GeyserSession.java | 5 ++ .../geyser/session/cache/TagCache.java | 10 +++- .../MerchantInventoryTranslator.java | 46 +++++++++++++++++++ .../entity/BedrockEntityEventTranslator.java | 14 +----- .../java/JavaUpdateTagsTranslator.java | 36 ++++++++++++++- 6 files changed, 111 insertions(+), 16 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/inventory/MerchantContainer.java b/core/src/main/java/org/geysermc/geyser/inventory/MerchantContainer.java index 7c0bcaf4d..315e6cb18 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/MerchantContainer.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/MerchantContainer.java @@ -31,15 +31,27 @@ import com.github.steveice10.mc.protocol.packet.ingame.clientbound.inventory.Cli import lombok.Getter; import lombok.Setter; import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.session.GeyserSession; -@Getter -@Setter public class MerchantContainer extends Container { + @Getter @Setter private Entity villager; + @Setter private VillagerTrade[] villagerTrades; + @Getter @Setter private ClientboundMerchantOffersPacket pendingOffersPacket; public MerchantContainer(String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory) { super(title, id, size, containerType, playerInventory); } + + public void onTradeSelected(GeyserSession session, int slot) { + if (villagerTrades != null && slot >= 0 && slot < villagerTrades.length) { + VillagerTrade trade = villagerTrades[slot]; + setItem(2, GeyserItemStack.from(trade.getOutput()), session); + // TODO this logic doesn't add up + session.getPlayerEntity().addFakeTradeExperience(trade.getXp()); + session.getPlayerEntity().updateBedrockMetadata(); + } + } } diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index 3a097f732..b886f8b20 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -361,6 +361,11 @@ public class GeyserSession implements GeyserConnection, CommandSender { @Setter private Int2ObjectMap stonecutterRecipes; + /** + * Whether to work around 1.13's different behavior in villager trading menus. + */ + @Setter + private boolean emulatePost1_14Logic = true; /** * Starting in 1.17, Java servers expect the carriedItem parameter of the serverbound click container * packet to be the current contents of the mouse after the transaction has been done. 1.16 expects the clicked slot diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/TagCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/TagCache.java index 549b2dbee..f2f1597fe 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/TagCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/TagCache.java @@ -30,6 +30,7 @@ import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.ints.IntLists; import org.geysermc.geyser.registry.type.BlockMapping; import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.session.GeyserSession; import java.util.Map; @@ -61,7 +62,7 @@ public class TagCache { clear(); } - public void loadPacket(ClientboundUpdateTagsPacket packet) { + public void loadPacket(GeyserSession session, ClientboundUpdateTagsPacket packet) { Map blockTags = packet.getTags().get("minecraft:block"); this.leaves = IntList.of(blockTags.get("minecraft:leaves")); this.wool = IntList.of(blockTags.get("minecraft:wool")); @@ -79,6 +80,13 @@ public class TagCache { this.flowers = IntList.of(itemTags.get("minecraft:flowers")); this.foxFood = IntList.of(itemTags.get("minecraft:fox_food")); this.piglinLoved = IntList.of(itemTags.get("minecraft:piglin_loved")); + + // Hack btw + boolean emulatePost1_14Logic = itemTags.get("minecraft:signs").length > 1; + session.setEmulatePost1_14Logic(emulatePost1_14Logic); + if (session.getGeyser().getLogger().isDebug()) { + session.getGeyser().getLogger().debug("Emulating post 1.14 villager logic for " + session.name() + "? " + emulatePost1_14Logic); + } } public void clear() { diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/MerchantInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/MerchantInventoryTranslator.java index 6b63056a3..84f904d98 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/MerchantInventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/MerchantInventoryTranslator.java @@ -26,14 +26,17 @@ package org.geysermc.geyser.translator.inventory; import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundSelectTradePacket; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityLinkData; import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; import com.nukkitx.protocol.bedrock.data.inventory.ItemStackRequest; import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; +import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.CraftRecipeStackRequestActionData; import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; import com.nukkitx.protocol.bedrock.packet.SetEntityLinkPacket; +import com.nukkitx.protocol.bedrock.v486.Bedrock_v486; import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.inventory.Inventory; @@ -44,6 +47,9 @@ import org.geysermc.geyser.inventory.BedrockContainerSlot; import org.geysermc.geyser.inventory.SlotType; import org.geysermc.geyser.inventory.updater.InventoryUpdater; import org.geysermc.geyser.inventory.updater.UIInventoryUpdater; +import org.geysermc.geyser.util.InventoryUtils; + +import java.util.concurrent.TimeUnit; public class MerchantInventoryTranslator extends BaseInventoryTranslator { private final InventoryUpdater updater; @@ -131,6 +137,46 @@ public class MerchantInventoryTranslator extends BaseInventoryTranslator { } } + @Override + public ItemStackResponsePacket.Response translateCraftingRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { + if (session.getUpstream().getProtocolVersion() < Bedrock_v486.V486_CODEC.getProtocolVersion()) { + return super.translateCraftingRequest(session, inventory, request); + } + + // Behavior as of 1.18.10. + // We set the net ID to the trade index + 1. This doesn't appear to cause issues and means we don't have to + // store a map of net ID to trade index on our end. + int tradeChoice = ((CraftRecipeStackRequestActionData) request.getActions()[0]).getRecipeNetworkId() - 1; + ServerboundSelectTradePacket packet = new ServerboundSelectTradePacket(tradeChoice); + session.sendDownstreamPacket(packet); + + if (session.isEmulatePost1_14Logic()) { + // 1.18 Java cooperates nicer than older versions + if (inventory instanceof MerchantContainer merchantInventory) { + merchantInventory.onTradeSelected(session, tradeChoice); + } + return translateRequest(session, inventory, request); + } else { + // 1.18 servers works fine without a workaround, but ViaVersion needs to work around 1.13 servers, + // so we need to work around that with the delay. Specifically they force a window refresh after a + // trade packet has been sent. + session.scheduleInEventLoop(() -> { + if (inventory instanceof MerchantContainer merchantInventory) { + merchantInventory.onTradeSelected(session, tradeChoice); + // Ignore output since we don't want to send a delayed response packet back to the client + translateRequest(session, inventory, request); + + // Resync items once more + updateInventory(session, inventory); + InventoryUtils.updateCursor(session); + } + }, 100, TimeUnit.MILLISECONDS); + + // Revert this request, for now + return rejectRequest(request); + } + } + @Override public ItemStackResponsePacket.Response translateAutoCraftingRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { // We're not crafting here diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/BedrockEntityEventTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/BedrockEntityEventTranslator.java index a42184750..b693b7f3c 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/BedrockEntityEventTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/BedrockEntityEventTranslator.java @@ -25,11 +25,8 @@ package org.geysermc.geyser.translator.protocol.bedrock.entity; -import com.github.steveice10.mc.protocol.data.game.inventory.VillagerTrade; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundSelectTradePacket; import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; -import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; -import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.inventory.Inventory; import org.geysermc.geyser.inventory.MerchantContainer; import org.geysermc.geyser.session.GeyserSession; @@ -50,21 +47,14 @@ public class BedrockEntityEventTranslator extends PacketTranslator { + // Not sent as of 1.18.10 ServerboundSelectTradePacket selectTradePacket = new ServerboundSelectTradePacket(packet.getData()); session.sendDownstreamPacket(selectTradePacket); session.scheduleInEventLoop(() -> { - SessionPlayerEntity villager = session.getPlayerEntity(); Inventory openInventory = session.getOpenInventory(); if (openInventory instanceof MerchantContainer merchantInventory) { - VillagerTrade[] trades = merchantInventory.getVillagerTrades(); - if (trades != null && packet.getData() >= 0 && packet.getData() < trades.length) { - VillagerTrade trade = merchantInventory.getVillagerTrades()[packet.getData()]; - openInventory.setItem(2, GeyserItemStack.from(trade.getOutput()), session); - // TODO this logic doesn't add up - villager.addFakeTradeExperience(trade.getXp()); - villager.updateBedrockMetadata(); - } + merchantInventory.onTradeSelected(session, packet.getData()); } }, 100, TimeUnit.MILLISECONDS); return; diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaUpdateTagsTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaUpdateTagsTranslator.java index 3d5bfc43a..9f1c24fd8 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaUpdateTagsTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaUpdateTagsTranslator.java @@ -30,11 +30,45 @@ import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + @Translator(packet = ClientboundUpdateTagsPacket.class) public class JavaUpdateTagsTranslator extends PacketTranslator { + private final Map> previous = new HashMap<>(); @Override public void translate(GeyserSession session, ClientboundUpdateTagsPacket packet) { - session.getTagCache().loadPacket(packet); + for (Map.Entry> entry : packet.getTags().entrySet().stream().sorted(Map.Entry.comparingByKey()).toList()) { + StringBuilder builder = new StringBuilder(); + builder.append(entry.getKey()).append("={"); + for (Map.Entry tag : entry.getValue().entrySet().stream().sorted(Map.Entry.comparingByKey()).toList()) { + builder.append(tag.getKey()).append('=').append(Arrays.toString(tag.getValue())).append(", "); + } + System.out.println(builder.append("}").toString()); + } + + if (previous.isEmpty()) { + previous.putAll(packet.getTags()); + } else { + for (Map.Entry> entry : packet.getTags().entrySet()) { + Map oldTags = previous.get(entry.getKey()); + for (Map.Entry newTag : entry.getValue().entrySet()) { + int[] oldValue = oldTags.get(newTag.getKey()); + if (oldValue == null) { + System.out.println("Tag " + newTag.getKey() + " not found!!"); + continue; + } + if (!Arrays.equals(Arrays.stream(oldValue).sorted().toArray(), Arrays.stream(newTag.getValue()).sorted().toArray())) { + System.out.println(entry.getKey() + ": " + newTag.getKey() + " has different values! " + Arrays.toString(Arrays.stream(oldValue).sorted().toArray()) + " " + Arrays.toString(Arrays.stream(newTag.getValue()).sorted().toArray())); + } + } + } + } + + session.getTagCache().loadPacket(session, packet); + + } } From 0ca77233edfd85aff29dad28cea9001acdbc007a Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 10 Feb 2022 20:15:46 -0500 Subject: [PATCH 025/110] oh no my extensive debugging setup --- .../java/JavaUpdateTagsTranslator.java | 34 ------------------- 1 file changed, 34 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaUpdateTagsTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaUpdateTagsTranslator.java index 9f1c24fd8..a899077f8 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaUpdateTagsTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaUpdateTagsTranslator.java @@ -30,45 +30,11 @@ import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; - @Translator(packet = ClientboundUpdateTagsPacket.class) public class JavaUpdateTagsTranslator extends PacketTranslator { - private final Map> previous = new HashMap<>(); @Override public void translate(GeyserSession session, ClientboundUpdateTagsPacket packet) { - for (Map.Entry> entry : packet.getTags().entrySet().stream().sorted(Map.Entry.comparingByKey()).toList()) { - StringBuilder builder = new StringBuilder(); - builder.append(entry.getKey()).append("={"); - for (Map.Entry tag : entry.getValue().entrySet().stream().sorted(Map.Entry.comparingByKey()).toList()) { - builder.append(tag.getKey()).append('=').append(Arrays.toString(tag.getValue())).append(", "); - } - System.out.println(builder.append("}").toString()); - } - - if (previous.isEmpty()) { - previous.putAll(packet.getTags()); - } else { - for (Map.Entry> entry : packet.getTags().entrySet()) { - Map oldTags = previous.get(entry.getKey()); - for (Map.Entry newTag : entry.getValue().entrySet()) { - int[] oldValue = oldTags.get(newTag.getKey()); - if (oldValue == null) { - System.out.println("Tag " + newTag.getKey() + " not found!!"); - continue; - } - if (!Arrays.equals(Arrays.stream(oldValue).sorted().toArray(), Arrays.stream(newTag.getValue()).sorted().toArray())) { - System.out.println(entry.getKey() + ": " + newTag.getKey() + " has different values! " + Arrays.toString(Arrays.stream(oldValue).sorted().toArray()) + " " + Arrays.toString(Arrays.stream(newTag.getValue()).sorted().toArray())); - } - } - } - } - session.getTagCache().loadPacket(session, packet); - - } } From 44af9da01da3bbade88bdd56e6b884266e917f7c Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 11 Feb 2022 13:37:19 -0500 Subject: [PATCH 026/110] Use axolotl tempt tag to determine axolotl food --- .../geyser/entity/type/living/animal/AxolotlEntity.java | 2 +- .../java/org/geysermc/geyser/session/cache/TagCache.java | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/AxolotlEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/AxolotlEntity.java index ec919a5c4..2ada1fe09 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/AxolotlEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/AxolotlEntity.java @@ -56,7 +56,7 @@ public class AxolotlEntity extends AnimalEntity { @Override public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) { - return javaIdentifierStripped.equals("tropical_fish_bucket"); + return session.getTagCache().isAxolotlTemptItem(mapping); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/TagCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/TagCache.java index f2f1597fe..0f73737bb 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/TagCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/TagCache.java @@ -53,6 +53,7 @@ public class TagCache { private IntList requiresDiamondTool; /* Items */ + private IntList axolotlTemptItems; private IntList flowers; private IntList foxFood; private IntList piglinLoved; @@ -77,6 +78,7 @@ public class TagCache { this.requiresDiamondTool = IntList.of(blockTags.get("minecraft:needs_diamond_tool")); Map itemTags = packet.getTags().get("minecraft:item"); + this.axolotlTemptItems = IntList.of(itemTags.get("minecraft:axolotl_tempt_items")); this.flowers = IntList.of(itemTags.get("minecraft:flowers")); this.foxFood = IntList.of(itemTags.get("minecraft:fox_food")); this.piglinLoved = IntList.of(itemTags.get("minecraft:piglin_loved")); @@ -102,11 +104,16 @@ public class TagCache { this.requiresIronTool = IntLists.emptyList(); this.requiresDiamondTool = IntLists.emptyList(); + this.axolotlTemptItems = IntLists.emptyList(); this.flowers = IntLists.emptyList(); this.foxFood = IntLists.emptyList(); this.piglinLoved = IntLists.emptyList(); } + public boolean isAxolotlTemptItem(ItemMapping itemMapping) { + return axolotlTemptItems.contains(itemMapping.getJavaId()); + } + public boolean isFlower(ItemMapping mapping) { return flowers.contains(mapping.getJavaId()); } From 31fd57a58d19829071859ef292fee706873d31fb Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 12 Feb 2022 21:44:51 -0500 Subject: [PATCH 027/110] Also apply villager fix for consoles --- .../MerchantInventoryTranslator.java | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/MerchantInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/MerchantInventoryTranslator.java index 84f904d98..248bd35b7 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/MerchantInventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/MerchantInventoryTranslator.java @@ -33,6 +33,7 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityLinkData; import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; import com.nukkitx.protocol.bedrock.data.inventory.ItemStackRequest; import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; +import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.AutoCraftRecipeStackRequestActionData; import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.CraftRecipeStackRequestActionData; import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; import com.nukkitx.protocol.bedrock.packet.SetEntityLinkPacket; @@ -147,6 +148,25 @@ public class MerchantInventoryTranslator extends BaseInventoryTranslator { // We set the net ID to the trade index + 1. This doesn't appear to cause issues and means we don't have to // store a map of net ID to trade index on our end. int tradeChoice = ((CraftRecipeStackRequestActionData) request.getActions()[0]).getRecipeNetworkId() - 1; + return handleTrade(session, inventory, request, tradeChoice); + } + + @Override + public ItemStackResponsePacket.Response translateAutoCraftingRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { + if (session.getUpstream().getProtocolVersion() < Bedrock_v486.V486_CODEC.getProtocolVersion()) { + // We're not crafting here + // Called at least by consoles when pressing a trade option button + return translateRequest(session, inventory, request); + } + + // 1.18.10 update - seems impossible to call without consoles/controller input + // We set the net ID to the trade index + 1. This doesn't appear to cause issues and means we don't have to + // store a map of net ID to trade index on our end. + int tradeChoice = ((AutoCraftRecipeStackRequestActionData) request.getActions()[0]).getRecipeNetworkId() - 1; + return handleTrade(session, inventory, request, tradeChoice); + } + + private ItemStackResponsePacket.Response handleTrade(GeyserSession session, Inventory inventory, ItemStackRequest request, int tradeChoice) { ServerboundSelectTradePacket packet = new ServerboundSelectTradePacket(tradeChoice); session.sendDownstreamPacket(packet); @@ -177,13 +197,6 @@ public class MerchantInventoryTranslator extends BaseInventoryTranslator { } } - @Override - public ItemStackResponsePacket.Response translateAutoCraftingRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { - // We're not crafting here - // Called at least by consoles when pressing a trade option button - return translateRequest(session, inventory, request); - } - @Override public void updateInventory(GeyserSession session, Inventory inventory) { updater.updateInventory(this, session, inventory); From 252348ebd8fc0097a415dc4cbb165ae101f42fd5 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 19 Feb 2022 12:07:30 -0500 Subject: [PATCH 028/110] Implement globe banner pattern translation Co-Authored-By: Konicai <71294714+Konicai@users.noreply.github.com> --- .../populator/ItemRegistryPopulator.java | 12 +++++++++--- .../inventory/item/BannerTranslator.java | 18 +++++------------- core/src/main/resources/mappings | 2 +- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java index 1b56a83de..209588d72 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java @@ -224,8 +224,14 @@ public class ItemRegistryPopulator { // This items has a mapping specifically for this version of the game mappingItem = entry.getValue(); } + + String bedrockIdentifier; if (javaIdentifier.equals("minecraft:music_disc_otherside") && palette.getValue().protocolVersion() <= Bedrock_v471.V471_CODEC.getProtocolVersion()) { - mappingItem.setBedrockIdentifier("minecraft:music_disc_pigstep"); + bedrockIdentifier = "minecraft:music_disc_pigstep"; + } else if (javaIdentifier.equals("minecraft:globe_banner_pattern") && palette.getValue().protocolVersion() < Bedrock_v486.V486_CODEC.getProtocolVersion()) { + bedrockIdentifier = "minecraft:banner_pattern"; + } else { + bedrockIdentifier = mappingItem.getBedrockIdentifier(); } if (usingFurnaceMinecart && javaIdentifier.equals("minecraft:furnace_minecart")) { @@ -233,7 +239,7 @@ public class ItemRegistryPopulator { itemIndex++; continue; } - String bedrockIdentifier = mappingItem.getBedrockIdentifier().intern(); + int bedrockId = bedrockIdentifierToId.getInt(bedrockIdentifier); if (bedrockId == Short.MIN_VALUE) { throw new RuntimeException("Missing Bedrock ID in mappings: " + bedrockIdentifier); @@ -358,7 +364,7 @@ public class ItemRegistryPopulator { ItemMapping.ItemMappingBuilder mappingBuilder = ItemMapping.builder() .javaIdentifier(javaIdentifier) .javaId(itemIndex) - .bedrockIdentifier(bedrockIdentifier) + .bedrockIdentifier(bedrockIdentifier.intern()) .bedrockId(bedrockId) .bedrockData(mappingItem.getBedrockData()) .bedrockBlockId(bedrockBlockId) diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/BannerTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/BannerTranslator.java index a5c3235a2..15f7c57ce 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/BannerTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/BannerTranslator.java @@ -37,6 +37,7 @@ import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMappings; +import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -96,10 +97,7 @@ public class BannerTranslator extends ItemTranslator { public static NbtList convertBannerPattern(ListTag patterns) { List tagsList = new ArrayList<>(); for (Tag patternTag : patterns.getValue()) { - NbtMap newPatternTag = getBedrockBannerPattern((CompoundTag) patternTag); - if (newPatternTag != null) { - tagsList.add(newPatternTag); - } + tagsList.add(getBedrockBannerPattern((CompoundTag) patternTag)); } return new NbtList<>(NbtType.COMPOUND, tagsList); @@ -111,17 +109,11 @@ public class BannerTranslator extends ItemTranslator { * @param pattern Java edition pattern nbt * @return The Bedrock edition format pattern nbt */ - public static NbtMap getBedrockBannerPattern(CompoundTag pattern) { - String patternName = (String) pattern.get("Pattern").getValue(); - - // Return null if its the globe pattern as it doesn't exist on bedrock - if (patternName.equals("glb")) { - return null; - } - + @Nonnull + private static NbtMap getBedrockBannerPattern(CompoundTag pattern) { return NbtMap.builder() .putInt("Color", 15 - (int) pattern.get("Color").getValue()) - .putString("Pattern", patternName) + .putString("Pattern", (String) pattern.get("Pattern").getValue()) .build(); } diff --git a/core/src/main/resources/mappings b/core/src/main/resources/mappings index b60cfcdd4..8620c9c46 160000 --- a/core/src/main/resources/mappings +++ b/core/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit b60cfcdd40cd58a93143b489fc9153a347e48c41 +Subproject commit 8620c9c4603c16b74cbe1d6630695d243679896b From 811ae178c97bd6b6b9505516796ebb3bff368009 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 21 Feb 2022 16:11:51 -0500 Subject: [PATCH 029/110] Store recipes in a more compact GeyserRecipe type This prevents us from storing some unnecessary data. Also removes some 1.11 recipe compatibility code that is essentially unusable. --- .../platform/spigot/GeyserSpigotPlugin.java | 20 +- .../GeyserSpigot1_11CraftingListener.java | 203 ------------------ .../geyser/inventory/recipe/GeyserRecipe.java | 36 ++++ .../inventory/recipe/GeyserShapedRecipe.java | 43 ++++ .../recipe/GeyserShapelessRecipe.java | 42 ++++ .../geysermc/geyser/registry/Registries.java | 3 +- .../populator/RecipeRegistryPopulator.java | 17 +- .../geyser/session/GeyserSession.java | 4 +- .../inventory/InventoryTranslator.java | 40 ++-- .../java/JavaUpdateRecipesTranslator.java | 9 +- .../JavaContainerSetSlotTranslator.java | 7 +- .../geysermc/geyser/util/InventoryUtils.java | 40 ++-- 12 files changed, 184 insertions(+), 280 deletions(-) delete mode 100644 bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/GeyserSpigot1_11CraftingListener.java create mode 100644 core/src/main/java/org/geysermc/geyser/inventory/recipe/GeyserRecipe.java create mode 100644 core/src/main/java/org/geysermc/geyser/inventory/recipe/GeyserShapedRecipe.java create mode 100644 core/src/main/java/org/geysermc/geyser/inventory/recipe/GeyserShapelessRecipe.java diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java index bdf28a203..aae6c599a 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java @@ -32,27 +32,26 @@ import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; import org.bukkit.Bukkit; import org.bukkit.plugin.java.JavaPlugin; import org.geysermc.common.PlatformType; -import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.Constants; import org.geysermc.geyser.GeyserBootstrap; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.adapters.spigot.SpigotAdapters; import org.geysermc.geyser.command.CommandManager; -import org.geysermc.geyser.session.auth.AuthType; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.dump.BootstrapDumpInfo; -import org.geysermc.geyser.network.MinecraftProtocol; import org.geysermc.geyser.level.WorldManager; +import org.geysermc.geyser.network.MinecraftProtocol; import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; import org.geysermc.geyser.ping.IGeyserPingPassthrough; -import org.geysermc.geyser.Constants; -import org.geysermc.geyser.util.FileUtils; -import org.geysermc.geyser.text.GeyserLocale; -import org.geysermc.geyser.adapters.spigot.SpigotAdapters; import org.geysermc.geyser.platform.spigot.command.GeyserSpigotCommandExecutor; import org.geysermc.geyser.platform.spigot.command.GeyserSpigotCommandManager; import org.geysermc.geyser.platform.spigot.command.SpigotCommandSender; import org.geysermc.geyser.platform.spigot.world.GeyserPistonListener; -import org.geysermc.geyser.platform.spigot.world.GeyserSpigot1_11CraftingListener; import org.geysermc.geyser.platform.spigot.world.GeyserSpigotBlockPlaceListener; import org.geysermc.geyser.platform.spigot.world.manager.*; +import org.geysermc.geyser.session.auth.AuthType; +import org.geysermc.geyser.text.GeyserLocale; +import org.geysermc.geyser.util.FileUtils; import java.io.File; import java.io.IOException; @@ -235,11 +234,6 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { Bukkit.getServer().getPluginManager().registerEvents(new GeyserPistonListener(geyser, this.geyserWorldManager), this); - if (isPre1_12) { - // Register events needed to send all recipes to the client - Bukkit.getServer().getPluginManager().registerEvents(new GeyserSpigot1_11CraftingListener(geyser), this); - } - this.getCommand("geyser").setExecutor(new GeyserSpigotCommandExecutor(geyser)); // Check to ensure the current setup can support the protocol version Geyser uses diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/GeyserSpigot1_11CraftingListener.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/GeyserSpigot1_11CraftingListener.java deleted file mode 100644 index 78a64e47b..000000000 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/GeyserSpigot1_11CraftingListener.java +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright (c) 2019-2022 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.platform.spigot.world; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; -import com.github.steveice10.mc.protocol.data.game.recipe.Ingredient; -import com.github.steveice10.mc.protocol.data.game.recipe.RecipeType; -import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapedRecipeData; -import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapelessRecipeData; -import com.nukkitx.protocol.bedrock.data.inventory.CraftingData; -import com.nukkitx.protocol.bedrock.data.inventory.ItemData; -import com.nukkitx.protocol.bedrock.packet.CraftingDataPacket; -import com.viaversion.viaversion.api.Via; -import com.viaversion.viaversion.api.data.MappingData; -import com.viaversion.viaversion.api.protocol.ProtocolPathEntry; -import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; -import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.Protocol1_13To1_12_2; -import com.viaversion.viaversion.util.Pair; -import org.bukkit.Bukkit; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerJoinEvent; -import org.bukkit.inventory.Recipe; -import org.bukkit.inventory.ShapedRecipe; -import org.bukkit.inventory.ShapelessRecipe; -import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.network.MinecraftProtocol; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.translator.inventory.item.ItemTranslator; -import org.geysermc.geyser.util.InventoryUtils; - -import java.util.*; - -/** - * Used to send all available recipes from the server to the client, as a valid recipe book packet won't be sent by the server. - * Requires ViaVersion. - */ -public class GeyserSpigot1_11CraftingListener implements Listener { - - private final GeyserImpl geyser; - /** - * Specific mapping data for 1.12 to 1.13. Used to convert the 1.12 item into 1.13. - */ - private final MappingData mappingData1_12to1_13; - /** - * The list of all protocols from the client's version to 1.13. - */ - private final List protocolList; - - public GeyserSpigot1_11CraftingListener(GeyserImpl geyser) { - this.geyser = geyser; - this.mappingData1_12to1_13 = Via.getManager().getProtocolManager().getProtocol(Protocol1_13To1_12_2.class).getMappingData(); - this.protocolList = Via.getManager().getProtocolManager().getProtocolPath(MinecraftProtocol.getJavaProtocolVersion(), - ProtocolVersion.v1_13.getVersion()); - } - - @EventHandler - public void onPlayerJoin(PlayerJoinEvent event) { - GeyserSession session = null; - for (GeyserSession otherSession : geyser.getSessionManager().getSessions().values()) { - if (otherSession.name().equals(event.getPlayer().getName())) { - session = otherSession; - break; - } - } - if (session == null) { - return; - } - - sendServerRecipes(session); - } - - public void sendServerRecipes(GeyserSession session) { - int netId = InventoryUtils.LAST_RECIPE_NET_ID; - - CraftingDataPacket craftingDataPacket = new CraftingDataPacket(); - craftingDataPacket.setCleanRecipes(true); - - Iterator recipeIterator = Bukkit.getServer().recipeIterator(); - while (recipeIterator.hasNext()) { - Recipe recipe = recipeIterator.next(); - - Pair outputs = translateToBedrock(session, recipe.getResult()); - ItemStack javaOutput = outputs.getKey(); - ItemData output = outputs.getValue(); - if (output == null || output.getId() == 0) continue; // If items make air we don't want that - - boolean isNotAllAir = false; // Check for all-air recipes - if (recipe instanceof ShapedRecipe shapedRecipe) { - int size = shapedRecipe.getShape().length * shapedRecipe.getShape()[0].length(); - Ingredient[] ingredients = new Ingredient[size]; - ItemData[] input = new ItemData[size]; - for (int i = 0; i < input.length; i++) { - // Index is converting char to integer, adding i then converting back to char based on ASCII code - Pair result = translateToBedrock(session, shapedRecipe.getIngredientMap().get((char) ('a' + i))); - ingredients[i] = new Ingredient(new ItemStack[]{result.getKey()}); - input[i] = result.getValue(); - isNotAllAir |= input[i].getId() != 0; - } - - if (!isNotAllAir) continue; - UUID uuid = UUID.randomUUID(); - // Add recipe to our internal cache - ShapedRecipeData data = new ShapedRecipeData(shapedRecipe.getShape()[0].length(), shapedRecipe.getShape().length, - "", ingredients, javaOutput); - session.getCraftingRecipes().put(netId, - new com.github.steveice10.mc.protocol.data.game.recipe.Recipe(RecipeType.CRAFTING_SHAPED, uuid.toString(), data)); - - // Add recipe for Bedrock - craftingDataPacket.getCraftingData().add(CraftingData.fromShaped(uuid.toString(), - shapedRecipe.getShape()[0].length(), shapedRecipe.getShape().length, Arrays.asList(input), - Collections.singletonList(output), uuid, "crafting_table", 0, netId++)); - } else if (recipe instanceof ShapelessRecipe shapelessRecipe) { - Ingredient[] ingredients = new Ingredient[shapelessRecipe.getIngredientList().size()]; - ItemData[] input = new ItemData[shapelessRecipe.getIngredientList().size()]; - - for (int i = 0; i < input.length; i++) { - Pair result = translateToBedrock(session, shapelessRecipe.getIngredientList().get(i)); - ingredients[i] = new Ingredient(new ItemStack[]{result.getKey()}); - input[i] = result.getValue(); - isNotAllAir |= input[i].getId() != 0; - } - - if (!isNotAllAir) continue; - UUID uuid = UUID.randomUUID(); - // Add recipe to our internal cache - ShapelessRecipeData data = new ShapelessRecipeData("", ingredients, javaOutput); - session.getCraftingRecipes().put(netId, - new com.github.steveice10.mc.protocol.data.game.recipe.Recipe(RecipeType.CRAFTING_SHAPELESS, uuid.toString(), data)); - - // Add recipe for Bedrock - craftingDataPacket.getCraftingData().add(CraftingData.fromShapeless(uuid.toString(), - Arrays.asList(input), Collections.singletonList(output), uuid, "crafting_table", 0, netId++)); - } - } - - session.sendUpstreamPacket(craftingDataPacket); - } - - @SuppressWarnings("deprecation") - private Pair translateToBedrock(GeyserSession session, org.bukkit.inventory.ItemStack itemStack) { - if (itemStack != null && itemStack.getData() != null) { - if (itemStack.getType().getId() == 0) { - return new Pair<>(null, ItemData.AIR); - } - - int legacyId = (itemStack.getType().getId() << 4) | (itemStack.getData().getData() & 0xFFFF); - - if (itemStack.getType().getId() == 355 && itemStack.getData().getData() == (byte) 0) { // Handle bed color since the server will always be pre-1.12 - legacyId = (itemStack.getType().getId() << 4) | ((byte) 14 & 0xFFFF); - } - - // old version -> 1.13 -> 1.13.1 -> 1.14 -> 1.15 -> 1.16 and so on - int itemId; - if (mappingData1_12to1_13.getItemMappings().containsKey(legacyId)) { - itemId = mappingData1_12to1_13.getNewItemId(legacyId); - } else if (mappingData1_12to1_13.getItemMappings().containsKey((itemStack.getType().getId() << 4) | (0))) { - itemId = mappingData1_12to1_13.getNewItemId((itemStack.getType().getId() << 4) | (0)); - } else { - // No ID found, just send back air - return new Pair<>(null, ItemData.AIR); - } - - for (int i = protocolList.size() - 1; i >= 0; i--) { - MappingData mappingData = protocolList.get(i).getProtocol().getMappingData(); - if (mappingData != null) { - itemId = mappingData.getNewItemId(itemId); - } - } - - ItemStack mcItemStack = new ItemStack(itemId, itemStack.getAmount()); - ItemData finalData = ItemTranslator.translateToBedrock(session, mcItemStack); - return new Pair<>(mcItemStack, finalData); - } - - // Empty slot, most likely - return new Pair<>(null, ItemData.AIR); - } - -} diff --git a/core/src/main/java/org/geysermc/geyser/inventory/recipe/GeyserRecipe.java b/core/src/main/java/org/geysermc/geyser/inventory/recipe/GeyserRecipe.java new file mode 100644 index 000000000..641d5ad94 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/inventory/recipe/GeyserRecipe.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2019-2022 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; + +/** + * A more compact version of {@link com.github.steveice10.mc.protocol.data.game.recipe.Recipe}. + */ +public interface GeyserRecipe { + /** + * Whether the recipe is flexible or not in which items can be placed where. + */ + boolean isShaped(); +} diff --git a/core/src/main/java/org/geysermc/geyser/inventory/recipe/GeyserShapedRecipe.java b/core/src/main/java/org/geysermc/geyser/inventory/recipe/GeyserShapedRecipe.java new file mode 100644 index 000000000..a011fef6d --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/inventory/recipe/GeyserShapedRecipe.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2019-2022 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 com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.github.steveice10.mc.protocol.data.game.recipe.Ingredient; +import com.github.steveice10.mc.protocol.data.game.recipe.RecipeType; +import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapedRecipeData; + +public record GeyserShapedRecipe(int width, int height, Ingredient[] ingredients, ItemStack result) implements GeyserRecipe { + + public GeyserShapedRecipe(ShapedRecipeData data) { + this(data.getWidth(), data.getHeight(), data.getIngredients(), data.getResult()); + } + + @Override + public boolean isShaped() { + return true; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/inventory/recipe/GeyserShapelessRecipe.java b/core/src/main/java/org/geysermc/geyser/inventory/recipe/GeyserShapelessRecipe.java new file mode 100644 index 000000000..6c7665bbb --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/inventory/recipe/GeyserShapelessRecipe.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2019-2022 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 com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.github.steveice10.mc.protocol.data.game.recipe.Ingredient; +import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapelessRecipeData; + +public record GeyserShapelessRecipe(Ingredient[] ingredients, ItemStack result) implements GeyserRecipe { + + public GeyserShapelessRecipe(ShapelessRecipeData data) { + this(data.getIngredients(), data.getResult()); + } + + @Override + public boolean isShaped() { + return false; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/registry/Registries.java b/core/src/main/java/org/geysermc/geyser/registry/Registries.java index 5a60351ce..20e9d3515 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/Registries.java +++ b/core/src/main/java/org/geysermc/geyser/registry/Registries.java @@ -43,6 +43,7 @@ import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.inventory.item.Enchantment.JavaEnchantment; +import org.geysermc.geyser.inventory.recipe.GeyserRecipe; import org.geysermc.geyser.registry.loader.*; import org.geysermc.geyser.registry.populator.ItemRegistryPopulator; import org.geysermc.geyser.registry.populator.PacketRegistryPopulator; @@ -141,7 +142,7 @@ public final class Registries { /** * A versioned registry holding all the recipes, with the net ID being the key, and {@link Recipe} as the value. */ - public static final VersionedRegistry> RECIPES = VersionedRegistry.create(RegistryLoaders.empty(Int2ObjectOpenHashMap::new)); + public static final VersionedRegistry> RECIPES = VersionedRegistry.create(RegistryLoaders.empty(Int2ObjectOpenHashMap::new)); /** * A mapped registry holding the available records, with the ID of the record being the key, and the {@link com.nukkitx.protocol.bedrock.data.SoundEvent} diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/RecipeRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/RecipeRegistryPopulator.java index f32aeef51..f0a215f2a 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/RecipeRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/RecipeRegistryPopulator.java @@ -28,10 +28,7 @@ package org.geysermc.geyser.registry.populator; import com.fasterxml.jackson.databind.JsonNode; import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.github.steveice10.mc.protocol.data.game.recipe.Ingredient; -import com.github.steveice10.mc.protocol.data.game.recipe.Recipe; import com.github.steveice10.mc.protocol.data.game.recipe.RecipeType; -import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapedRecipeData; -import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapelessRecipeData; import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtUtils; import com.nukkitx.protocol.bedrock.data.inventory.CraftingData; @@ -40,6 +37,9 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import org.geysermc.geyser.GeyserImpl; +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.registry.Registries; import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMappings; @@ -71,7 +71,7 @@ public class RecipeRegistryPopulator { // Make a bit of an assumption here that the last recipe net ID will be equivalent between all versions LAST_RECIPE_NET_ID = currentRecipeId; Map> craftingData = new EnumMap<>(RecipeType.class); - Int2ObjectMap recipes = new Int2ObjectOpenHashMap<>(); + Int2ObjectMap recipes = new Int2ObjectOpenHashMap<>(); craftingData.put(RecipeType.CRAFTING_SPECIAL_BOOKCLONING, Collections.singletonList(CraftingData.fromMulti(UUID.fromString("d1ca6b84-338e-4f2f-9c6b-76cc8b4bd98d"), ++LAST_RECIPE_NET_ID))); @@ -124,7 +124,7 @@ public class RecipeRegistryPopulator { * @param recipes a list of all the recipes * @return the {@link CraftingData} to send to the Bedrock client. */ - private static CraftingData getCraftingDataFromJsonNode(JsonNode node, Int2ObjectMap recipes, ItemMappings mappings) { + private static CraftingData getCraftingDataFromJsonNode(JsonNode node, Int2ObjectMap recipes, ItemMappings mappings) { int netId = ++LAST_RECIPE_NET_ID; int type = node.get("bedrockRecipeType").asInt(); JsonNode outputNode = node.get("output"); @@ -165,9 +165,8 @@ public class RecipeRegistryPopulator { for (ItemData input : inputs) { ingredients.add(new Ingredient(new ItemStack[]{ItemTranslator.translateToJava(input, mappings)})); } - ShapedRecipeData data = new ShapedRecipeData(shape.get(0).length(), shape.size(), "crafting_table", + GeyserRecipe recipe = new GeyserShapedRecipe(shape.get(0).length(), shape.size(), ingredients.toArray(new Ingredient[0]), ItemTranslator.translateToJava(output, mappings)); - Recipe recipe = new Recipe(RecipeType.CRAFTING_SHAPED, "", data); recipes.put(netId, recipe); /* Convert end */ @@ -185,9 +184,7 @@ public class RecipeRegistryPopulator { for (ItemData input : inputs) { ingredients.add(new Ingredient(new ItemStack[]{ItemTranslator.translateToJava(input, mappings)})); } - ShapelessRecipeData data = new ShapelessRecipeData("crafting_table", - ingredients.toArray(new Ingredient[0]), ItemTranslator.translateToJava(output, mappings)); - Recipe recipe = new Recipe(RecipeType.CRAFTING_SHAPELESS, "", data); + GeyserRecipe recipe = new GeyserShapelessRecipe(ingredients.toArray(new Ingredient[0]), ItemTranslator.translateToJava(output, mappings)); recipes.put(netId, recipe); /* Convert end */ diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index b886f8b20..c2e6ae6f6 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -39,7 +39,6 @@ import com.github.steveice10.mc.protocol.data.UnexpectedEncryptionException; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import com.github.steveice10.mc.protocol.data.game.entity.player.HandPreference; -import com.github.steveice10.mc.protocol.data.game.recipe.Recipe; import com.github.steveice10.mc.protocol.data.game.setting.ChatVisibility; import com.github.steveice10.mc.protocol.data.game.setting.SkinPart; import com.github.steveice10.mc.protocol.data.game.statistic.CustomStatistic; @@ -94,6 +93,7 @@ import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; import org.geysermc.geyser.entity.type.player.SkullPlayerEntity; import org.geysermc.geyser.inventory.Inventory; import org.geysermc.geyser.inventory.PlayerInventory; +import org.geysermc.geyser.inventory.recipe.GeyserRecipe; import org.geysermc.geyser.level.WorldManager; import org.geysermc.geyser.level.physics.CollisionManager; import org.geysermc.geyser.network.netty.LocalSession; @@ -350,7 +350,7 @@ public class GeyserSession implements GeyserConnection, CommandSender { private Entity mouseoverEntity; @Setter - private Int2ObjectMap craftingRecipes; + private Int2ObjectMap craftingRecipes; private final Set unlockedRecipes; private final AtomicInteger lastRecipeNetId; diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/InventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/InventoryTranslator.java index e6a9faf74..b48709595 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/InventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/InventoryTranslator.java @@ -28,9 +28,6 @@ package org.geysermc.geyser.translator.inventory; import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType; import com.github.steveice10.mc.protocol.data.game.recipe.Ingredient; -import com.github.steveice10.mc.protocol.data.game.recipe.Recipe; -import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapedRecipeData; -import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapelessRecipeData; import com.github.steveice10.opennbt.tag.builtin.IntTag; import com.github.steveice10.opennbt.tag.builtin.Tag; import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; @@ -45,6 +42,9 @@ import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.inventory.*; import org.geysermc.geyser.inventory.click.Click; import org.geysermc.geyser.inventory.click.ClickPlan; +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.session.GeyserSession; import org.geysermc.geyser.translator.inventory.chest.DoubleChestInventoryTranslator; import org.geysermc.geyser.translator.inventory.chest.SingleChestInventoryTranslator; @@ -535,7 +535,6 @@ public abstract class InventoryTranslator { } int gridDimensions = gridSize == 4 ? 2 : 3; - Recipe recipe; Ingredient[] ingredients = new Ingredient[0]; ItemStack output = null; int recipeWidth = 0; @@ -564,7 +563,7 @@ public abstract class InventoryTranslator { craftState = CraftState.RECIPE_ID; int recipeId = autoCraftAction.getRecipeNetworkId(); - recipe = session.getCraftingRecipes().get(recipeId); + GeyserRecipe recipe = session.getCraftingRecipes().get(recipeId); if (recipe == null) { return rejectRequest(request); } @@ -578,24 +577,21 @@ public abstract class InventoryTranslator { } } - switch (recipe.getType()) { - case CRAFTING_SHAPED -> { - ShapedRecipeData shapedData = (ShapedRecipeData) recipe.getData(); - ingredients = shapedData.getIngredients(); - recipeWidth = shapedData.getWidth(); - output = shapedData.getResult(); - if (shapedData.getWidth() > gridDimensions || shapedData.getHeight() > gridDimensions) { - return rejectRequest(request); - } + if (recipe.isShaped()) { + GeyserShapedRecipe shapedRecipe = (GeyserShapedRecipe) recipe; + ingredients = shapedRecipe.ingredients(); + recipeWidth = shapedRecipe.width(); + output = shapedRecipe.result(); + if (recipeWidth > gridDimensions || shapedRecipe.height() > gridDimensions) { + return rejectRequest(request); } - case CRAFTING_SHAPELESS -> { - ShapelessRecipeData shapelessData = (ShapelessRecipeData) recipe.getData(); - ingredients = shapelessData.getIngredients(); - recipeWidth = gridDimensions; - output = shapelessData.getResult(); - if (ingredients.length > gridSize) { - return rejectRequest(request); - } + } else { + GeyserShapelessRecipe shapelessRecipe = (GeyserShapelessRecipe) recipe; + ingredients = shapelessRecipe.ingredients(); + recipeWidth = gridDimensions; + output = shapelessRecipe.result(); + if (ingredients.length > gridSize) { + return rejectRequest(request); } } break; diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaUpdateRecipesTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaUpdateRecipesTranslator.java index c3c8abfb4..4d7a1617a 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaUpdateRecipesTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaUpdateRecipesTranslator.java @@ -42,6 +42,9 @@ import com.nukkitx.protocol.bedrock.v486.Bedrock_v486; import it.unimi.dsi.fastutil.ints.*; import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; +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.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; @@ -80,7 +83,7 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator= Bedrock_v486.V486_CODEC.getProtocolVersion(); - Int2ObjectMap recipeMap = new Int2ObjectOpenHashMap<>(Registries.RECIPES.forVersion(session.getUpstream().getProtocolVersion())); + Int2ObjectMap recipeMap = new Int2ObjectOpenHashMap<>(Registries.RECIPES.forVersion(session.getUpstream().getProtocolVersion())); Int2ObjectMap> unsortedStonecutterData = new Int2ObjectOpenHashMap<>(); CraftingDataPacket craftingDataPacket = new CraftingDataPacket(); craftingDataPacket.setCleanRecipes(true); @@ -100,7 +103,7 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator { @@ -118,7 +121,7 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator { diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerSetSlotTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerSetSlotTranslator.java index 4bb2a8e60..36307e7bd 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerSetSlotTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerSetSlotTranslator.java @@ -27,9 +27,6 @@ package org.geysermc.geyser.translator.protocol.java.inventory; import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.github.steveice10.mc.protocol.data.game.recipe.Ingredient; -import com.github.steveice10.mc.protocol.data.game.recipe.Recipe; -import com.github.steveice10.mc.protocol.data.game.recipe.RecipeType; -import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapedRecipeData; import com.github.steveice10.mc.protocol.packet.ingame.clientbound.inventory.ClientboundContainerSetSlotPacket; import com.nukkitx.protocol.bedrock.data.inventory.ContainerId; import com.nukkitx.protocol.bedrock.data.inventory.CraftingData; @@ -38,6 +35,7 @@ import com.nukkitx.protocol.bedrock.packet.CraftingDataPacket; import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.inventory.Inventory; +import org.geysermc.geyser.inventory.recipe.GeyserShapedRecipe; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.inventory.InventoryTranslator; import org.geysermc.geyser.translator.inventory.PlayerInventoryTranslator; @@ -165,9 +163,8 @@ public class JavaContainerSetSlotTranslator extends PacketTranslator inventoryGetter, + public static GeyserRecipe getValidRecipe(final GeyserSession session, final @Nullable ItemStack output, final IntFunction inventoryGetter, final int gridDimensions, final int firstRow, final int height, final int firstCol, final int width) { int nonAirCount = 0; // Used for shapeless recipes for amount of items needed in recipe for (int row = firstRow; row < height + firstRow; row++) { @@ -373,14 +372,14 @@ public class InventoryUtils { } recipes: - for (Recipe recipe : session.getCraftingRecipes().values()) { - if (recipe.getType() == RecipeType.CRAFTING_SHAPED) { - ShapedRecipeData data = (ShapedRecipeData) recipe.getData(); - if (output != null && !data.getResult().equals(output)) { + for (GeyserRecipe recipe : session.getCraftingRecipes().values()) { + if (recipe.isShaped()) { + GeyserShapedRecipe shapedRecipe = (GeyserShapedRecipe) recipe; + if (output != null && !shapedRecipe.result().equals(output)) { continue; } - Ingredient[] ingredients = data.getIngredients(); - if (data.getWidth() != width || data.getHeight() != height || width * height != ingredients.length) { + Ingredient[] ingredients = shapedRecipe.ingredients(); + if (shapedRecipe.width() != width || shapedRecipe.height() != height || width * height != ingredients.length) { continue; } @@ -397,18 +396,17 @@ public class InventoryUtils { continue; } } - return recipe; - } else if (recipe.getType() == RecipeType.CRAFTING_SHAPELESS) { - ShapelessRecipeData data = (ShapelessRecipeData) recipe.getData(); - if (output != null && !data.getResult().equals(output)) { + } else { + GeyserShapelessRecipe data = (GeyserShapelessRecipe) recipe; + if (output != null && !data.result().equals(output)) { continue; } - if (nonAirCount != data.getIngredients().length) { + if (nonAirCount != data.ingredients().length) { // There is an amount of items on the crafting table that is not the same as the ingredient count so this is invalid continue; } - for (int i = 0; i < data.getIngredients().length; i++) { - Ingredient ingredient = data.getIngredients()[i]; + for (int i = 0; i < data.ingredients().length; i++) { + Ingredient ingredient = data.ingredients()[i]; for (ItemStack itemStack : ingredient.getOptions()) { boolean inventoryHasItem = false; // Iterate only over the crafting table to find this item @@ -432,8 +430,8 @@ public class InventoryUtils { } } } - return recipe; } + return recipe; } return null; } From 619ce53c50cba96f21a52aca85196087be366af9 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 21 Feb 2022 20:51:31 -0500 Subject: [PATCH 030/110] Javadoc fix for Registries.RECIPES --- .../src/main/java/org/geysermc/geyser/registry/Registries.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/registry/Registries.java b/core/src/main/java/org/geysermc/geyser/registry/Registries.java index 20e9d3515..0b59492d3 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/Registries.java +++ b/core/src/main/java/org/geysermc/geyser/registry/Registries.java @@ -29,7 +29,6 @@ import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType; import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType; import com.github.steveice10.mc.protocol.data.game.level.event.SoundEvent; import com.github.steveice10.mc.protocol.data.game.level.particle.ParticleType; -import com.github.steveice10.mc.protocol.data.game.recipe.Recipe; import com.github.steveice10.mc.protocol.data.game.recipe.RecipeType; import com.github.steveice10.packetlib.packet.Packet; import com.nukkitx.nbt.NbtMap; @@ -140,7 +139,7 @@ public final class Registries { public static final SimpleRegistry> POTION_MIXES; /** - * A versioned registry holding all the recipes, with the net ID being the key, and {@link Recipe} as the value. + * A versioned registry holding all the recipes, with the net ID being the key, and {@link GeyserRecipe} as the value. */ public static final VersionedRegistry> RECIPES = VersionedRegistry.create(RegistryLoaders.empty(Int2ObjectOpenHashMap::new)); From 7bd5b595650322d8915558731a282985235d1d07 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 21 Feb 2022 21:19:19 -0500 Subject: [PATCH 031/110] Allow the Jigsaw to operate correctly on 1.14.4 Fixes #2852 --- .../geyser/level/block/BlockStateValues.java | 20 +++++++++++++++++++ .../JigsawBlockBlockEntityTranslator.java | 19 +++++++++++++----- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/level/block/BlockStateValues.java b/core/src/main/java/org/geysermc/geyser/level/block/BlockStateValues.java index 3d43b066b..48d0e80e0 100644 --- a/core/src/main/java/org/geysermc/geyser/level/block/BlockStateValues.java +++ b/core/src/main/java/org/geysermc/geyser/level/block/BlockStateValues.java @@ -38,6 +38,8 @@ import org.geysermc.geyser.util.collection.FixedInt2ByteMap; import org.geysermc.geyser.util.collection.FixedInt2IntMap; import org.geysermc.geyser.util.collection.LecternHasBookMap; +import java.util.Locale; + /** * Used for block entities if the Java block state contains Bedrock block information. */ @@ -47,6 +49,7 @@ public final class BlockStateValues { private static final Int2ByteMap COMMAND_BLOCK_VALUES = new Int2ByteOpenHashMap(); private static final Int2ObjectMap DOUBLE_CHEST_VALUES = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap FLOWER_POT_VALUES = new Int2ObjectOpenHashMap<>(); + private static final IntSet HORIZONTAL_FACING_JIGSAWS = new IntOpenHashSet(); private static final LecternHasBookMap LECTERN_BOOK_STATES = new LecternHasBookMap(); private static final Int2IntMap NOTEBLOCK_PITCHES = new FixedInt2IntMap(); private static final Int2BooleanMap PISTON_VALUES = new Int2BooleanOpenHashMap(); @@ -170,12 +173,22 @@ public final class BlockStateValues { JsonNode shulkerDirection = blockData.get("shulker_direction"); if (shulkerDirection != null) { BlockStateValues.SHULKERBOX_DIRECTIONS.put(javaBlockState, (byte) shulkerDirection.intValue()); + return; } if (javaId.startsWith("minecraft:water")) { String strLevel = javaId.substring(javaId.lastIndexOf("level=") + 6, javaId.length() - 1); int level = Integer.parseInt(strLevel); WATER_LEVEL.put(javaBlockState, level); + return; + } + + if (javaId.startsWith("minecraft:jigsaw[orientation=")) { + String blockStateData = javaId.substring(javaId.indexOf("orientation=") + "orientation=".length(), javaId.lastIndexOf('_')); + Direction direction = Direction.valueOf(blockStateData.toUpperCase(Locale.ROOT)); + if (direction.isHorizontal()) { + HORIZONTAL_FACING_JIGSAWS.add(javaBlockState); + } } } @@ -230,6 +243,13 @@ public final class BlockStateValues { return FLOWER_POT_VALUES; } + /** + * @return a set of all forward-facing jigsaws, to use as a fallback if NBT is missing. + */ + public static IntSet getHorizontalFacingJigsaws() { + return HORIZONTAL_FACING_JIGSAWS; + } + /** * @return the lectern book state map pointing to book present state */ diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/JigsawBlockBlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/JigsawBlockBlockEntityTranslator.java index a1e990138..bb036a1b0 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/JigsawBlockBlockEntityTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/JigsawBlockBlockEntityTranslator.java @@ -28,16 +28,25 @@ package org.geysermc.geyser.translator.level.block.entity; import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.StringTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; import com.nukkitx.nbt.NbtMapBuilder; +import org.geysermc.geyser.level.block.BlockStateValues; @BlockEntity(type = BlockEntityType.JIGSAW) -public class JigsawBlockBlockEntityTranslator extends BlockEntityTranslator { +public class JigsawBlockBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { @Override public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { - builder.put("joint", ((StringTag) tag.get("joint")).getValue()); - builder.put("name", ((StringTag) tag.get("name")).getValue()); - builder.put("target_pool", ((StringTag) tag.get("pool")).getValue()); + Tag jointTag = tag.get("joint"); + if (jointTag instanceof StringTag) { + builder.put("joint", ((StringTag) jointTag).getValue()); + } else { + // Tag is not present in at least 1.14.4 Paper + // Minecraft 1.18.1 deliberately has a fallback here, but not for any other value + builder.put("joint", BlockStateValues.getHorizontalFacingJigsaws().contains(blockState) ? "aligned" : "rollable"); + } + builder.put("name", getOrDefault(tag.get("name"), "")); + builder.put("target_pool", getOrDefault(tag.get("pool"), "")); builder.put("final_state", ((StringTag) tag.get("final_state")).getValue()); - builder.put("target", ((StringTag) tag.get("target")).getValue()); + builder.put("target", getOrDefault(tag.get("target"), "")); } } From 7d1ec5c41a16c176a579f5105121b15149b9b295 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 24 Feb 2022 22:49:10 -0500 Subject: [PATCH 032/110] Support offhand interactions with entities --- .../geyser/entity/EntityDefinitions.java | 24 +- .../geyser/entity/InteractiveTagManager.java | 293 ------------------ .../geyser/entity/type/BoatEntity.java | 24 ++ .../type/CommandBlockMinecartEntity.java | 32 ++ .../geysermc/geyser/entity/type/Entity.java | 73 ++++- .../entity/type/FurnaceMinecartEntity.java | 9 + .../geyser/entity/type/ItemFrameEntity.java | 8 + .../geyser/entity/type/LeashKnotEntity.java | 7 + .../geyser/entity/type/LivingEntity.java | 35 +++ .../geyser/entity/type/MinecartEntity.java | 39 +++ .../type/living/AbstractFishEntity.java | 14 + .../entity/type/living/AmbientEntity.java | 5 + .../entity/type/living/ArmorStandEntity.java | 13 + .../entity/type/living/DolphinEntity.java | 66 ++++ .../entity/type/living/IronGolemEntity.java | 17 + .../geyser/entity/type/living/MobEntity.java | 98 ++++++ .../entity/type/living/SlimeEntity.java | 5 + .../entity/type/living/SnowGolemEntity.java | 24 ++ .../entity/type/living/SquidEntity.java | 5 + .../entity/type/living/WaterEntity.java | 5 + .../type/living/animal/AnimalEntity.java | 36 +++ .../type/living/animal/AxolotlEntity.java | 19 ++ .../entity/type/living/animal/CowEntity.java | 65 ++++ .../entity/type/living/animal/GoatEntity.java | 18 +- .../type/living/animal/HoglinEntity.java | 10 + .../type/living/animal/MooshroomEntity.java | 47 +++ .../type/living/animal/OcelotEntity.java | 27 ++ .../type/living/animal/PandaEntity.java | 81 ++++- .../entity/type/living/animal/PigEntity.java | 39 +++ .../type/living/animal/SheepEntity.java | 54 +++- .../type/living/animal/StriderEntity.java | 38 +++ .../type/living/animal/TurtleEntity.java | 5 + .../animal/horse/AbstractHorseEntity.java | 156 +++++++++- .../animal/horse/ChestedHorseEntity.java | 20 ++ .../type/living/animal/horse/LlamaEntity.java | 2 +- .../animal/horse/SkeletonHorseEntity.java | 54 ++++ .../animal/horse/ZombieHorseEntity.java | 54 ++++ .../living/animal/tameable/CatEntity.java | 28 ++ .../living/animal/tameable/ParrotEntity.java | 47 ++- .../animal/tameable/TameableEntity.java | 13 +- .../living/animal/tameable/WolfEntity.java | 46 +++ .../merchant/AbstractMerchantEntity.java | 39 +++ .../type/living/merchant/VillagerEntity.java | 65 ++-- .../type/living/monster/CreeperEntity.java | 27 ++ .../living/monster/EnderDragonEntity.java | 9 +- .../type/living/monster/GhastEntity.java | 5 + .../type/living/monster/MonsterEntity.java | 5 + .../type/living/monster/PhantomEntity.java | 5 + .../type/living/monster/PiglinEntity.java | 30 ++ .../type/living/monster/ShulkerEntity.java | 5 + .../type/living/monster/ZoglinEntity.java | 10 + .../living/monster/ZombieVillagerEntity.java | 35 ++- .../geyser/inventory/PlayerInventory.java | 5 + .../inventory/item/StoredItemMappings.java | 22 ++ .../populator/ItemRegistryPopulator.java | 22 +- .../registry/type/GeyserMappingItem.java | 2 + .../geyser/registry/type/ItemMapping.java | 4 +- .../geyser/session/GeyserSession.java | 19 +- .../geyser/session/cache/TagCache.java | 17 + .../geyser/session/cache/WorldBorder.java | 11 +- ...BedrockInventoryTransactionTranslator.java | 80 ++--- .../BedrockMobEquipmentTranslator.java | 3 +- .../player/BedrockInteractTranslator.java | 3 +- .../entity/JavaSetEntityDataTranslator.java | 6 +- .../player/JavaPlayerAbilitiesTranslator.java | 1 + .../EntitySoundInteractionTranslator.java | 93 ------ .../translator/sound/SoundTranslator.java | 11 - .../FeedBabySoundInteractionTranslator.java | 57 ---- .../MilkEntitySoundInteractionTranslator.java | 65 ---- .../org/geysermc/geyser/util/EntityUtils.java | 33 +- .../geyser/util/InteractionResult.java | 55 ++++ .../geysermc/geyser/util/InteractiveTag.java | 91 ++++++ .../geysermc/geyser/util/InventoryUtils.java | 7 + .../org/geysermc/geyser/util/ItemUtils.java | 17 + 74 files changed, 1860 insertions(+), 654 deletions(-) delete mode 100644 core/src/main/java/org/geysermc/geyser/entity/InteractiveTagManager.java create mode 100644 core/src/main/java/org/geysermc/geyser/entity/type/living/DolphinEntity.java create mode 100644 core/src/main/java/org/geysermc/geyser/entity/type/living/animal/CowEntity.java create mode 100644 core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/SkeletonHorseEntity.java create mode 100644 core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/ZombieHorseEntity.java delete mode 100644 core/src/main/java/org/geysermc/geyser/translator/sound/EntitySoundInteractionTranslator.java delete mode 100644 core/src/main/java/org/geysermc/geyser/translator/sound/entity/FeedBabySoundInteractionTranslator.java delete mode 100644 core/src/main/java/org/geysermc/geyser/translator/sound/entity/MilkEntitySoundInteractionTranslator.java create mode 100644 core/src/main/java/org/geysermc/geyser/util/InteractionResult.java create mode 100644 core/src/main/java/org/geysermc/geyser/util/InteractiveTag.java diff --git a/core/src/main/java/org/geysermc/geyser/entity/EntityDefinitions.java b/core/src/main/java/org/geysermc/geyser/entity/EntityDefinitions.java index 9e4124cdc..1de571c94 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/EntityDefinitions.java +++ b/core/src/main/java/org/geysermc/geyser/entity/EntityDefinitions.java @@ -65,9 +65,9 @@ public final class EntityDefinitions { public static final EntityDefinition CHICKEN; public static final EntityDefinition COD; public static final EntityDefinition COMMAND_BLOCK_MINECART; - public static final EntityDefinition COW; + public static final EntityDefinition COW; public static final EntityDefinition CREEPER; - public static final EntityDefinition DOLPHIN; + public static final EntityDefinition DOLPHIN; public static final EntityDefinition DONKEY; public static final EntityDefinition DRAGON_FIREBALL; public static final EntityDefinition DROWNED; @@ -132,7 +132,7 @@ public final class EntityDefinitions { public static final EntityDefinition SHULKER_BULLET; public static final EntityDefinition SILVERFISH; public static final EntityDefinition SKELETON; - public static final EntityDefinition SKELETON_HORSE; + public static final EntityDefinition SKELETON_HORSE; public static final EntityDefinition SLIME; public static final EntityDefinition SMALL_FIREBALL; public static final EntityDefinition SNOWBALL; @@ -160,7 +160,7 @@ public final class EntityDefinitions { public static final EntityDefinition WOLF; public static final EntityDefinition ZOGLIN; public static final EntityDefinition ZOMBIE; - public static final EntityDefinition ZOMBIE_HORSE; + public static final EntityDefinition ZOMBIE_HORSE; public static final EntityDefinition ZOMBIE_VILLAGER; public static final EntityDefinition ZOMBIFIED_PIGLIN; @@ -459,7 +459,7 @@ public final class EntityDefinitions { .addTranslator(MetadataType.BOOLEAN, (entity, entityMetadata) -> entity.setFlag(EntityFlag.POWERED, ((BooleanEntityMetadata) entityMetadata).getPrimitiveValue())) .addTranslator(MetadataType.BOOLEAN, CreeperEntity::setIgnited) .build(); - DOLPHIN = EntityDefinition.inherited(WaterEntity::new, mobEntityBase) + DOLPHIN = EntityDefinition.inherited(DolphinEntity::new, mobEntityBase) .type(EntityType.DOLPHIN) .height(0.6f).width(0.9f) //TODO check @@ -723,7 +723,7 @@ public final class EntityDefinitions { .type(EntityType.CHICKEN) .height(0.7f).width(0.4f) .build(); - COW = EntityDefinition.inherited(AnimalEntity::new, ageableEntityBase) + COW = EntityDefinition.inherited(CowEntity::new, ageableEntityBase) .type(EntityType.COW) .height(1.4f).width(0.9f) .build(); @@ -745,14 +745,14 @@ public final class EntityDefinitions { .height(1.3f).width(0.9f) .addTranslator(MetadataType.BOOLEAN, GoatEntity::setScreamer) .build(); - MOOSHROOM = EntityDefinition.inherited(MooshroomEntity::new, ageableEntityBase) // TODO remove class + MOOSHROOM = EntityDefinition.inherited(MooshroomEntity::new, ageableEntityBase) .type(EntityType.MOOSHROOM) .height(1.4f).width(0.9f) - .addTranslator(MetadataType.STRING, (entity, entityMetadata) -> entity.getDirtyMetadata().put(EntityData.VARIANT, entityMetadata.getValue().equals("brown") ? 1 : 0)) + .addTranslator(MetadataType.STRING, MooshroomEntity::setVariant) .build(); OCELOT = EntityDefinition.inherited(OcelotEntity::new, ageableEntityBase) .type(EntityType.OCELOT) - .height(0.35f).width(0.3f) + .height(0.7f).width(0.6f) .addTranslator(MetadataType.BOOLEAN, (ocelotEntity, entityMetadata) -> ocelotEntity.setFlag(EntityFlag.TRUSTING, ((BooleanEntityMetadata) entityMetadata).getPrimitiveValue())) .build(); PANDA = EntityDefinition.inherited(PandaEntity::new, ageableEntityBase) @@ -783,7 +783,7 @@ public final class EntityDefinitions { .build(); SHEEP = EntityDefinition.inherited(SheepEntity::new, ageableEntityBase) .type(EntityType.SHEEP) - .heightAndWidth(0.9f) + .height(1.3f).width(0.9f) .addTranslator(MetadataType.BYTE, SheepEntity::setSheepFlags) .build(); STRIDER = EntityDefinition.inherited(StriderEntity::new, ageableEntityBase) @@ -832,11 +832,11 @@ public final class EntityDefinitions { .height(1.6f).width(1.3965f) .addTranslator(MetadataType.INT, HorseEntity::setHorseVariant) .build(); - SKELETON_HORSE = EntityDefinition.inherited(abstractHorseEntityBase.factory(), abstractHorseEntityBase) + SKELETON_HORSE = EntityDefinition.inherited(SkeletonHorseEntity::new, abstractHorseEntityBase) .type(EntityType.SKELETON_HORSE) .height(1.6f).width(1.3965f) .build(); - ZOMBIE_HORSE = EntityDefinition.inherited(abstractHorseEntityBase.factory(), abstractHorseEntityBase) + ZOMBIE_HORSE = EntityDefinition.inherited(ZombieHorseEntity::new, abstractHorseEntityBase) .type(EntityType.ZOMBIE_HORSE) .height(1.6f).width(1.3965f) .build(); diff --git a/core/src/main/java/org/geysermc/geyser/entity/InteractiveTagManager.java b/core/src/main/java/org/geysermc/geyser/entity/InteractiveTagManager.java deleted file mode 100644 index 0bc91cfcd..000000000 --- a/core/src/main/java/org/geysermc/geyser/entity/InteractiveTagManager.java +++ /dev/null @@ -1,293 +0,0 @@ -/* - * Copyright (c) 2019-2022 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.entity; - -import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType; -import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import lombok.Getter; -import org.geysermc.geyser.entity.type.Entity; -import org.geysermc.geyser.entity.type.living.MobEntity; -import org.geysermc.geyser.entity.type.living.animal.AnimalEntity; -import org.geysermc.geyser.entity.type.living.animal.horse.HorseEntity; -import org.geysermc.geyser.entity.type.living.animal.tameable.CatEntity; -import org.geysermc.geyser.entity.type.living.animal.tameable.WolfEntity; -import org.geysermc.geyser.entity.type.living.merchant.VillagerEntity; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.registry.type.ItemMapping; - -import java.util.EnumSet; -import java.util.Set; - -public class InteractiveTagManager { - /** - * All entity types that can be leashed on Java Edition - */ - private static final Set LEASHABLE_MOB_TYPES = EnumSet.of(EntityType.AXOLOTL, EntityType.BEE, EntityType.CAT, EntityType.CHICKEN, - EntityType.COW, EntityType.DOLPHIN, EntityType.DONKEY, EntityType.FOX, EntityType.GOAT, EntityType.GLOW_SQUID, EntityType.HOGLIN, - EntityType.HORSE, EntityType.SKELETON_HORSE, EntityType.ZOMBIE_HORSE, EntityType.IRON_GOLEM, EntityType.LLAMA, - EntityType.TRADER_LLAMA, EntityType.MOOSHROOM, EntityType.MULE, EntityType.OCELOT, EntityType.PARROT, EntityType.PIG, - EntityType.POLAR_BEAR, EntityType.RABBIT, EntityType.SHEEP, EntityType.SNOW_GOLEM, EntityType.SQUID, EntityType.STRIDER, - EntityType.WOLF, EntityType.ZOGLIN); - - private static final Set SADDLEABLE_WHEN_TAMED_MOB_TYPES = EnumSet.of(EntityType.DONKEY, EntityType.HORSE, - EntityType.ZOMBIE_HORSE, EntityType.MULE); - - /** - * Update the suggestion that the client currently has on their screen for this entity (for example, "Feed" or "Ride") - * - * @param session the Bedrock client session - * @param interactEntity the entity that the client is currently facing. - */ - public static void updateTag(GeyserSession session, Entity interactEntity) { - ItemMapping mapping = session.getPlayerInventory().getItemInHand().getMapping(session); - String javaIdentifierStripped = mapping.getJavaIdentifier().replace("minecraft:", ""); - EntityType entityType = interactEntity.getDefinition().entityType(); - if (entityType == null) { - // Likely a technical entity; we don't need to worry about this - return; - } - - InteractiveTag interactiveTag = InteractiveTag.NONE; - - if (interactEntity instanceof MobEntity mobEntity && mobEntity.getLeashHolderBedrockId() == session.getPlayerEntity().getGeyserId()) { - // Unleash the entity - interactiveTag = InteractiveTag.REMOVE_LEASH; - } else if (javaIdentifierStripped.equals("saddle") && !interactEntity.getFlag(EntityFlag.SADDLED) && - ((SADDLEABLE_WHEN_TAMED_MOB_TYPES.contains(entityType) && interactEntity.getFlag(EntityFlag.TAMED) && !session.isSneaking()) || - entityType == EntityType.PIG || entityType == EntityType.STRIDER)) { - // Entity can be saddled and the conditions meet (entity can be saddled and, if needed, is tamed) - interactiveTag = InteractiveTag.SADDLE; - } else if (javaIdentifierStripped.equals("name_tag") && session.getPlayerInventory().getItemInHand().getNbt() != null && - session.getPlayerInventory().getItemInHand().getNbt().contains("display")) { - // Holding a named name tag - interactiveTag = InteractiveTag.NAME; - } else if (interactEntity instanceof MobEntity mobEntity &&javaIdentifierStripped.equals("lead") - && LEASHABLE_MOB_TYPES.contains(entityType) && mobEntity.getLeashHolderBedrockId() == -1L) { - // Holding a leash and the mob is leashable for sure - // (Plugins can change this behavior so that's something to look into in the far far future) - interactiveTag = InteractiveTag.LEASH; - } else if (interactEntity instanceof AnimalEntity && ((AnimalEntity) interactEntity).canEat(javaIdentifierStripped, mapping)) { - // This animal can be fed - interactiveTag = InteractiveTag.FEED; - } else { - switch (entityType) { - case BOAT: - if (interactEntity.getPassengers().size() < 2) { - interactiveTag = InteractiveTag.BOARD_BOAT; - } - break; - case CAT: - if (interactEntity.getFlag(EntityFlag.TAMED) && - ((CatEntity) interactEntity).getOwnerBedrockId() == session.getPlayerEntity().getGeyserId()) { - // Tamed and owned by player - can sit/stand - interactiveTag = interactEntity.getFlag(EntityFlag.SITTING) ? InteractiveTag.STAND : InteractiveTag.SIT; - break; - } - break; - case MOOSHROOM: - // Shear the mooshroom - if (javaIdentifierStripped.equals("shears")) { - interactiveTag = InteractiveTag.MOOSHROOM_SHEAR; - break; - } - // Bowls are acceptable here - else if (javaIdentifierStripped.equals("bowl")) { - interactiveTag = InteractiveTag.MOOSHROOM_MILK_STEW; - break; - } - // Fall down to COW as this works on mooshrooms - case COW: - if (javaIdentifierStripped.equals("bucket")) { - // Milk the cow - interactiveTag = InteractiveTag.MILK; - } - break; - case CREEPER: - if (javaIdentifierStripped.equals("flint_and_steel")) { - // Today I learned that you can ignite a creeper with flint and steel! Huh. - interactiveTag = InteractiveTag.IGNITE_CREEPER; - } - break; - case DONKEY: - case LLAMA: - case MULE: - if (interactEntity.getFlag(EntityFlag.TAMED) && !interactEntity.getFlag(EntityFlag.CHESTED) - && javaIdentifierStripped.equals("chest")) { - // Can attach a chest - interactiveTag = InteractiveTag.ATTACH_CHEST; - break; - } - // Intentional fall-through - case HORSE: - case SKELETON_HORSE: - case TRADER_LLAMA: - case ZOMBIE_HORSE: - boolean tamed = interactEntity.getFlag(EntityFlag.TAMED); - if (session.isSneaking() && tamed && (interactEntity instanceof HorseEntity || interactEntity.getFlag(EntityFlag.CHESTED))) { - interactiveTag = InteractiveTag.OPEN_CONTAINER; - break; - } - if (!interactEntity.getFlag(EntityFlag.BABY)) { - // Can't ride a baby - if (tamed) { - interactiveTag = InteractiveTag.RIDE_HORSE; - } else if (mapping.getJavaId() == 0) { - // Can't hide an untamed entity without having your hand empty - interactiveTag = InteractiveTag.MOUNT; - } - } - break; - case MINECART: - if (interactEntity.getPassengers().isEmpty()) { - interactiveTag = InteractiveTag.RIDE_MINECART; - } - break; - case CHEST_MINECART: - case COMMAND_BLOCK_MINECART: - case HOPPER_MINECART: - interactiveTag = InteractiveTag.OPEN_CONTAINER; - break; - case PIG: - if (interactEntity.getFlag(EntityFlag.SADDLED)) { - interactiveTag = InteractiveTag.MOUNT; - } - break; - case PIGLIN: - if (!interactEntity.getFlag(EntityFlag.BABY) && javaIdentifierStripped.equals("gold_ingot")) { - interactiveTag = InteractiveTag.BARTER; - } - break; - case SHEEP: - if (!interactEntity.getFlag(EntityFlag.SHEARED)) { - if (javaIdentifierStripped.equals("shears")) { - // Shear the sheep - interactiveTag = InteractiveTag.SHEAR; - } else if (javaIdentifierStripped.contains("_dye")) { - // Dye the sheep - interactiveTag = InteractiveTag.DYE; - } - } - break; - case STRIDER: - if (interactEntity.getFlag(EntityFlag.SADDLED)) { - interactiveTag = InteractiveTag.RIDE_STRIDER; - } - break; - case VILLAGER: - VillagerEntity villager = (VillagerEntity) interactEntity; - if (villager.isCanTradeWith() && !villager.isBaby()) { // Not a nitwit, has a profession and is not a baby - interactiveTag = InteractiveTag.TRADE; - } - break; - case WANDERING_TRADER: - interactiveTag = InteractiveTag.TRADE; // Since you can always trade with a wandering villager, presumably. - break; - case WOLF: - if (javaIdentifierStripped.equals("bone") && !interactEntity.getFlag(EntityFlag.TAMED)) { - // Bone and untamed - can tame - interactiveTag = InteractiveTag.TAME; - } else if (interactEntity.getFlag(EntityFlag.TAMED) && - ((WolfEntity) interactEntity).getOwnerBedrockId() == session.getPlayerEntity().getGeyserId()) { - // Tamed and owned by player - can sit/stand - interactiveTag = interactEntity.getFlag(EntityFlag.SITTING) ? InteractiveTag.STAND : InteractiveTag.SIT; - } - break; - case ZOMBIE_VILLAGER: - // We can't guarantee the existence of the weakness effect so we just always show it. - if (javaIdentifierStripped.equals("golden_apple")) { - interactiveTag = InteractiveTag.CURE; - } - break; - default: - break; - } - } - session.getPlayerEntity().getDirtyMetadata().put(EntityData.INTERACTIVE_TAG, interactiveTag.getValue()); - session.getPlayerEntity().updateBedrockMetadata(); - } - - /** - * All interactive tags in enum form. For potential API usage. - */ - public enum InteractiveTag { - NONE((Void) null), - IGNITE_CREEPER("creeper"), - EDIT, - LEAVE_BOAT("exit.boat"), - FEED, - FISH("fishing"), - MILK, - MOOSHROOM_SHEAR("mooshear"), - MOOSHROOM_MILK_STEW("moostew"), - BOARD_BOAT("ride.boat"), - RIDE_MINECART("ride.minecart"), - RIDE_HORSE("ride.horse"), - RIDE_STRIDER("ride.strider"), - SHEAR, - SIT, - STAND, - TALK, - TAME, - DYE, - CURE, - OPEN_CONTAINER("opencontainer"), - CREATE_MAP("createMap"), - TAKE_PICTURE("takepicture"), - SADDLE, - MOUNT, - BOOST, - WRITE, - LEASH, - REMOVE_LEASH("unleash"), - NAME, - ATTACH_CHEST("attachchest"), - TRADE, - POSE_ARMOR_STAND("armorstand.pose"), - EQUIP_ARMOR_STAND("armorstand.equip"), - READ, - WAKE_VILLAGER("wakevillager"), - BARTER; - - /** - * The full string that should be passed on to the client. - */ - @Getter - private final String value; - - InteractiveTag(Void isNone) { - this.value = ""; - } - - InteractiveTag(String value) { - this.value = "action.interact." + value; - } - - InteractiveTag() { - this.value = "action.interact." + name().toLowerCase(); - } - } -} diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/BoatEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/BoatEntity.java index ddff746d6..6ce490bc2 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/BoatEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/BoatEntity.java @@ -27,6 +27,7 @@ package org.geysermc.geyser.entity.type; import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata; import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.packet.AnimatePacket; @@ -35,6 +36,8 @@ import lombok.Getter; import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; import java.util.UUID; import java.util.concurrent.TimeUnit; @@ -158,6 +161,27 @@ public class BoatEntity extends Entity { } } + @Override + protected InteractiveTag testInteraction(Hand hand) { + if (session.isSneaking()) { + return InteractiveTag.NONE; + } else if (passengers.size() < 2) { + return InteractiveTag.BOARD_BOAT; + } else { + return InteractiveTag.NONE; + } + } + + @Override + public InteractionResult interact(Hand hand) { + if (session.isSneaking()) { + return InteractionResult.PASS; + } else { + // TODO: the client also checks for "out of control" ticks + return InteractionResult.SUCCESS; + } + } + private void updateLeftPaddle(GeyserSession session, Entity rower) { if (isPaddlingLeft) { paddleTimeLeft += ROWING_SPEED; diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/CommandBlockMinecartEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/CommandBlockMinecartEntity.java index 36c050d1b..251eb98a0 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/CommandBlockMinecartEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/CommandBlockMinecartEntity.java @@ -25,10 +25,16 @@ package org.geysermc.geyser.entity.type; +import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket; import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; import java.util.UUID; @@ -55,4 +61,30 @@ public class CommandBlockMinecartEntity extends DefaultBlockMinecartEntity { dirtyMetadata.put(EntityData.DISPLAY_ITEM, session.getBlockMappings().getCommandBlockRuntimeId()); dirtyMetadata.put(EntityData.DISPLAY_OFFSET, 6); } + + @Override + protected InteractiveTag testInteraction(Hand hand) { + if (session.canUseCommandBlocks()) { + return InteractiveTag.OPEN_CONTAINER; + } else { + return InteractiveTag.NONE; + } + } + + @Override + public InteractionResult interact(Hand hand) { + if (session.canUseCommandBlocks()) { + // Client-side GUI required + ContainerOpenPacket openPacket = new ContainerOpenPacket(); + openPacket.setBlockPosition(Vector3i.ZERO); + openPacket.setId((byte) 1); + openPacket.setType(ContainerType.COMMAND_BLOCK); + openPacket.setUniqueEntityId(geyserId); + session.sendUpstreamPacket(openPacket); + + return InteractionResult.SUCCESS; + } else { + return InteractionResult.PASS; + } + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java b/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java index adeccdd01..270f69ee0 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java @@ -30,15 +30,14 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose; import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata; import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata; import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.data.entity.EntityFlags; -import com.nukkitx.protocol.bedrock.packet.AddEntityPacket; -import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket; -import com.nukkitx.protocol.bedrock.packet.RemoveEntityPacket; -import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket; +import com.nukkitx.protocol.bedrock.packet.*; import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; @@ -48,6 +47,8 @@ import org.geysermc.geyser.entity.GeyserDirtyMetadata; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.text.MessageTranslator; import org.geysermc.geyser.util.EntityUtils; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; import org.geysermc.geyser.util.MathUtils; import java.util.Collections; @@ -467,12 +468,68 @@ public class Entity { } } + public boolean isAlive() { + return this.valid; + } + + /** + * Update the suggestion that the client currently has on their screen for this entity (for example, "Feed" or "Ride") + */ + public final void updateInteractiveTag() { + InteractiveTag tag = InteractiveTag.NONE; + for (Hand hand: EntityUtils.HANDS) { + tag = testInteraction(hand); + if (tag != InteractiveTag.NONE) { + break; + } + } + session.getPlayerEntity().getDirtyMetadata().put(EntityData.INTERACTIVE_TAG, tag.getValue()); + session.getPlayerEntity().updateBedrockMetadata(); + } + + /** + * Test interacting with the given hand to see if we should send a tag to the Bedrock client. + * Should usually mirror {@link #interact(Hand)} without any side effects. + */ + protected InteractiveTag testInteraction(Hand hand) { + return InteractiveTag.NONE; + } + + /** + * Simulates interacting with an entity. The code here should mirror Java Edition code to the best of its ability, + * to ensure packet parity as well as functionality parity (such as sound effect responses). + */ + public InteractionResult interact(Hand hand) { + return InteractionResult.PASS; + } + + /** + * Simulates interacting with this entity at a specific click point. As of Java Edition 1.18.1, this is only used for armor stands. + */ + public InteractionResult interactAt(Hand hand) { + return InteractionResult.PASS; + } + + /** + * Send an entity event of the specified type to the Bedrock player from this entity. + */ + public final void playEntityEvent(EntityEventType type) { + playEntityEvent(type, 0); + } + + /** + * Send an entity event of the specified type with the specified data to the Bedrock player from this entity. + */ + public final void playEntityEvent(EntityEventType type, int data) { + EntityEventPacket packet = new EntityEventPacket(); + packet.setRuntimeEntityId(geyserId); + packet.setType(type); + packet.setData(data); + session.sendUpstreamPacket(packet); + } + @SuppressWarnings("unchecked") public I as(Class entityClass) { return entityClass.isInstance(this) ? (I) this : null; } - - public boolean is(Class entityClass) { - return entityClass.isInstance(this); - } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/FurnaceMinecartEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/FurnaceMinecartEntity.java index 9b7c79de4..dbd9bf91f 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/FurnaceMinecartEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/FurnaceMinecartEntity.java @@ -26,11 +26,13 @@ package org.geysermc.geyser.entity.type; import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.level.block.BlockStateValues; +import org.geysermc.geyser.util.InteractionResult; import java.util.UUID; @@ -42,6 +44,7 @@ public class FurnaceMinecartEntity extends DefaultBlockMinecartEntity { } public void setHasFuel(BooleanEntityMetadata entityMetadata) { + // Note: Java ticks this entity and gives it particles if it has fuel hasFuel = entityMetadata.getPrimitiveValue(); updateDefaultBlockMetadata(); } @@ -51,4 +54,10 @@ public class FurnaceMinecartEntity extends DefaultBlockMinecartEntity { dirtyMetadata.put(EntityData.DISPLAY_ITEM, session.getBlockMappings().getBedrockBlockId(hasFuel ? BlockStateValues.JAVA_FURNACE_LIT_ID : BlockStateValues.JAVA_FURNACE_ID)); dirtyMetadata.put(EntityData.DISPLAY_OFFSET, 6); } + + @Override + public InteractionResult interact(Hand hand) { + // Always works since you can "push" it this way + return InteractionResult.SUCCESS; + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/ItemFrameEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/ItemFrameEntity.java index 69aac5a26..9cfa22a1f 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/ItemFrameEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/ItemFrameEntity.java @@ -29,6 +29,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadat import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata; import com.github.steveice10.mc.protocol.data.game.entity.object.Direction; +import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3i; @@ -42,6 +43,8 @@ import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.inventory.item.ItemTranslator; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InventoryUtils; import java.util.UUID; @@ -205,6 +208,11 @@ public class ItemFrameEntity extends Entity { changed = false; } + @Override + public InteractionResult interact(Hand hand) { + return InventoryUtils.isEmpty(heldItem) && session.getPlayerInventory().getItemInHand(hand).isEmpty() ? InteractionResult.PASS : InteractionResult.SUCCESS; + } + /** * Finds the Java entity ID of an item frame from its Bedrock position. * @param position position of item frame in Bedrock. diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/LeashKnotEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/LeashKnotEntity.java index 28fe7d5bc..4ff1dfe7c 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/LeashKnotEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/LeashKnotEntity.java @@ -25,9 +25,11 @@ package org.geysermc.geyser.entity.type; +import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; import com.nukkitx.math.vector.Vector3f; import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.InteractionResult; import java.util.UUID; @@ -38,4 +40,9 @@ public class LeashKnotEntity extends Entity { super(session, entityId, geyserId, uuid, definition, position.add(0.5f, 0.25f, 0.5f), motion, yaw, pitch, headYaw); } + @Override + public InteractionResult interact(Hand hand) { + // Un-leashing the knot + return InteractionResult.SUCCESS; + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java index bc553f56c..a5214854e 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java @@ -33,6 +33,9 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata; import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.FloatEntityMetadata; import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.StringTag; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.AttributeData; @@ -48,10 +51,12 @@ import lombok.Getter; import lombok.Setter; import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.entity.attribute.GeyserAttributeType; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.util.AttributeUtils; import org.geysermc.geyser.util.ChunkUtils; +import org.geysermc.geyser.util.InteractionResult; import java.util.ArrayList; import java.util.Collections; @@ -169,6 +174,36 @@ public class LivingEntity extends Entity { return new AttributeData(GeyserAttributeType.HEALTH.getBedrockIdentifier(), 0f, this.maxHealth, (float) Math.ceil(this.health), this.maxHealth); } + @Override + public boolean isAlive() { + return this.valid && health > 0f; + } + + @Override + public InteractionResult interact(Hand hand) { + GeyserItemStack itemStack = session.getPlayerInventory().getItemInHand(hand); + if (itemStack.getJavaId() == session.getItemMappings().getStoredItems().nameTag()) { + InteractionResult result = checkInteractWithNameTag(itemStack); + if (result.consumesAction()) { + return result; + } + } + + return super.interact(hand); + } + + /** + * Checks to see if a nametag interaction would go through. + */ + protected final InteractionResult checkInteractWithNameTag(GeyserItemStack itemStack) { + CompoundTag nbt = itemStack.getNbt(); + if (nbt != null && nbt.get("display") instanceof CompoundTag displayTag && displayTag.get("Name") instanceof StringTag) { + // The mob shall be named + return InteractionResult.SUCCESS; + } + return InteractionResult.PASS; + } + public void updateArmor(GeyserSession session) { if (!valid) return; diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/MinecartEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/MinecartEntity.java index 80fc2a62e..a427d6a43 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/MinecartEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/MinecartEntity.java @@ -27,10 +27,14 @@ package org.geysermc.geyser.entity.type; import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata; import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; import java.util.UUID; @@ -64,4 +68,39 @@ public class MinecartEntity extends Entity { // Note: minecart rotation on rails does not care about the actual rotation value return Vector3f.from(0, yaw, 0); } + + @Override + protected InteractiveTag testInteraction(Hand hand) { + if (definition == EntityDefinitions.CHEST_MINECART || definition == EntityDefinitions.HOPPER_MINECART) { + return InteractiveTag.OPEN_CONTAINER; + } else { + if (session.isSneaking()) { + return InteractiveTag.NONE; + } else if (!passengers.isEmpty()) { + // Can't enter if someone is inside + return InteractiveTag.NONE; + } else { + // Attempt to enter + return InteractiveTag.RIDE_MINECART; + } + } + } + + @Override + public InteractionResult interact(Hand hand) { + if (definition == EntityDefinitions.CHEST_MINECART || definition == EntityDefinitions.HOPPER_MINECART) { + // Opening the UI of this minecart + return InteractionResult.SUCCESS; + } else { + if (session.isSneaking()) { + return InteractionResult.PASS; + } else if (!passengers.isEmpty()) { + // Can't enter if someone is inside + return InteractionResult.PASS; + } else { + // Attempt to enter + return InteractionResult.SUCCESS; + } + } + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/AbstractFishEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/AbstractFishEntity.java index dae1c76e6..f8e8c7091 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/AbstractFishEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/AbstractFishEntity.java @@ -28,8 +28,12 @@ package org.geysermc.geyser.entity.type.living; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.EntityUtils; +import org.geysermc.geyser.util.InteractionResult; +import javax.annotation.Nonnull; import java.util.UUID; public class AbstractFishEntity extends WaterEntity { @@ -42,4 +46,14 @@ public class AbstractFishEntity extends WaterEntity { setFlag(EntityFlag.CAN_CLIMB, false); setFlag(EntityFlag.HAS_GRAVITY, false); } + + @Nonnull + @Override + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + if (EntityUtils.attemptToBucket(session, this, itemInHand)) { + return InteractionResult.SUCCESS; + } else { + return super.mobInteract(itemInHand); + } + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/AmbientEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/AmbientEntity.java index 9dc5dca07..d4c627a8e 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/AmbientEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/AmbientEntity.java @@ -36,4 +36,9 @@ public class AmbientEntity extends MobEntity { public AmbientEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } + + @Override + protected boolean canBeLeashed() { + return false; + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/ArmorStandEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/ArmorStandEntity.java index 10086be9c..9c7e6d107 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/ArmorStandEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/ArmorStandEntity.java @@ -28,6 +28,8 @@ package org.geysermc.geyser.entity.type.living; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Rotation; import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; +import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; @@ -39,6 +41,7 @@ import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.type.LivingEntity; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.InteractionResult; import java.util.Optional; import java.util.UUID; @@ -237,6 +240,16 @@ public class ArmorStandEntity extends LivingEntity { } } + @Override + public InteractionResult interactAt(Hand hand) { + if (!isMarker && session.getPlayerInventory().getItemInHand(hand).getJavaId() != session.getItemMappings().getStoredItems().nameTag()) { + // Java Edition returns SUCCESS if in spectator mode, but this is overrided with an earlier check on the client + return InteractionResult.CONSUME; + } else { + return InteractionResult.PASS; + } + } + @Override public void setHelmet(ItemData helmet) { super.setHelmet(helmet); diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/DolphinEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/DolphinEntity.java new file mode 100644 index 000000000..7085547f8 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/DolphinEntity.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2019-2022 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.entity.type.living; + +import com.nukkitx.math.vector.Vector3f; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.inventory.GeyserItemStack; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; + +import javax.annotation.Nonnull; +import java.util.UUID; + +public class DolphinEntity extends WaterEntity { + public DolphinEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } + + @Override + protected boolean canBeLeashed() { + return true; + } + + @Nonnull + @Override + protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) { + if (!itemInHand.isEmpty() && session.getTagCache().isFish(itemInHand)) { + return InteractiveTag.FEED; + } + return super.testMobInteraction(itemInHand); + } + + @Nonnull + @Override + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + if (!itemInHand.isEmpty() && session.getTagCache().isFish(itemInHand)) { + // Feed + return InteractionResult.SUCCESS; + } + return super.mobInteract(itemInHand); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/IronGolemEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/IronGolemEntity.java index 0acdb960f..4ab36b00e 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/IronGolemEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/IronGolemEntity.java @@ -29,8 +29,11 @@ import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.InteractionResult; +import javax.annotation.Nonnull; import java.util.UUID; public class IronGolemEntity extends GolemEntity { @@ -42,4 +45,18 @@ public class IronGolemEntity extends GolemEntity { // Required, or else the overlay is black dirtyMetadata.put(EntityData.COLOR_2, (byte) 0); } + + @Nonnull + @Override + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + if (itemInHand.getJavaId() == session.getItemMappings().getStoredItems().ironIngot()) { + if (health < maxHealth) { + // Healing the iron golem + return InteractionResult.SUCCESS; + } else { + return InteractionResult.PASS; + } + } + return super.mobInteract(itemInHand); + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/MobEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/MobEntity.java index 54d652f32..8734f8bd1 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/MobEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/MobEntity.java @@ -26,14 +26,21 @@ package org.geysermc.geyser.entity.type.living; import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import lombok.Getter; import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.entity.type.LivingEntity; +import org.geysermc.geyser.inventory.GeyserItemStack; +import org.geysermc.geyser.inventory.item.StoredItemMappings; +import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; +import javax.annotation.Nonnull; import java.util.UUID; public class MobEntity extends LivingEntity { @@ -62,4 +69,95 @@ public class MobEntity extends LivingEntity { this.leashHolderBedrockId = bedrockId; dirtyMetadata.put(EntityData.LEASH_HOLDER_EID, bedrockId); } + + @Override + protected final InteractiveTag testInteraction(Hand hand) { + if (!isAlive()) { + // dead lol + return InteractiveTag.NONE; + } else if (leashHolderBedrockId == session.getPlayerEntity().getGeyserId()) { + return InteractiveTag.REMOVE_LEASH; + } else { + GeyserItemStack itemStack = session.getPlayerInventory().getItemInHand(hand); + StoredItemMappings storedItems = session.getItemMappings().getStoredItems(); + if (itemStack.getJavaId() == storedItems.lead() && canBeLeashed()) { + // We shall leash + return InteractiveTag.LEASH; + } else if (itemStack.getJavaId() == storedItems.nameTag()) { + InteractionResult result = checkInteractWithNameTag(itemStack); + if (result.consumesAction()) { + return InteractiveTag.NAME; + } + } + + InteractiveTag tag = testMobInteraction(itemStack); + return tag != InteractiveTag.NONE ? tag : super.testInteraction(hand); + } + } + + @Override + public final InteractionResult interact(Hand hand) { + if (!isAlive()) { + // dead lol + return InteractionResult.PASS; + } else if (leashHolderBedrockId == session.getPlayerEntity().getGeyserId()) { + // TODO looks like the client assumes it will go through and removes the attachment itself? + return InteractionResult.SUCCESS; + } else { + GeyserItemStack itemInHand = session.getPlayerInventory().getItemInHand(hand); + InteractionResult result = checkPriorityInteractions(itemInHand); + if (result.consumesAction()) { + return result; + } else { + InteractionResult mobResult = mobInteract(itemInHand); + return mobResult.consumesAction() ? mobResult : super.interact(hand); + } + } + } + + private InteractionResult checkPriorityInteractions(GeyserItemStack itemInHand) { + StoredItemMappings storedItems = session.getItemMappings().getStoredItems(); + if (itemInHand.getJavaId() == storedItems.lead() && canBeLeashed()) { + // We shall leash + return InteractionResult.SUCCESS; + } else if (itemInHand.getJavaId() == storedItems.nameTag()) { + InteractionResult result = checkInteractWithNameTag(itemInHand); + if (result.consumesAction()) { + return result; + } + } else { + ItemMapping mapping = itemInHand.getMapping(session); + if (mapping.getJavaIdentifier().endsWith("_spawn_egg")) { + // Using the spawn egg on this entity to create a child + return InteractionResult.CONSUME; + } + } + + return InteractionResult.PASS; + } + + @Nonnull + protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) { + return InteractiveTag.NONE; + } + + @Nonnull + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + return InteractionResult.PASS; + } + + protected boolean canBeLeashed() { + return isNotLeashed() && !isEnemy(); + } + + protected final boolean isNotLeashed() { + return leashHolderBedrockId == -1L; + } + + /** + * Returns if the entity is hostile. Used to determine if it can be leashed. + */ + protected boolean isEnemy() { + return false; + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/SlimeEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/SlimeEntity.java index 60e639415..26cf2d627 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/SlimeEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/SlimeEntity.java @@ -42,4 +42,9 @@ public class SlimeEntity extends MobEntity { public void setScale(IntEntityMetadata entityMetadata) { dirtyMetadata.put(EntityData.SCALE, 0.10f + entityMetadata.getPrimitiveValue()); } + + @Override + protected boolean isEnemy() { + return true; + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/SnowGolemEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/SnowGolemEntity.java index 10ddb48f4..794f71c04 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/SnowGolemEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/SnowGolemEntity.java @@ -29,8 +29,12 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEnti import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; +import javax.annotation.Nonnull; import java.util.UUID; public class SnowGolemEntity extends GolemEntity { @@ -44,4 +48,24 @@ public class SnowGolemEntity extends GolemEntity { // Handle the visibility of the pumpkin setFlag(EntityFlag.SHEARED, (xd & 0x10) != 0x10); } + + @Nonnull + @Override + protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) { + if (session.getItemMappings().getStoredItems().shears() == itemInHand.getJavaId() && isAlive() && !getFlag(EntityFlag.SHEARED)) { + // Shearing the snow golem + return InteractiveTag.SHEAR; + } + return InteractiveTag.NONE; + } + + @Nonnull + @Override + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + if (session.getItemMappings().getStoredItems().shears() == itemInHand.getJavaId() && isAlive() && !getFlag(EntityFlag.SHEARED)) { + // Shearing the snow golem + return InteractionResult.SUCCESS; + } + return InteractionResult.PASS; + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/SquidEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/SquidEntity.java index 0f860ae60..c81cf68de 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/SquidEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/SquidEntity.java @@ -120,6 +120,11 @@ public class SquidEntity extends WaterEntity implements Tickable { return Vector3f.from(pitch, yaw, yaw); } + @Override + protected boolean canBeLeashed() { + return isNotLeashed(); + } + private void checkInWater() { if (getFlag(EntityFlag.RIDING)) { inWater = false; diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/WaterEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/WaterEntity.java index 5adbd50a9..44275a7b1 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/WaterEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/WaterEntity.java @@ -36,4 +36,9 @@ public class WaterEntity extends CreatureEntity { public WaterEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } + + @Override + protected boolean canBeLeashed() { + return false; + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/AnimalEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/AnimalEntity.java index 2d1787932..64f41c5ad 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/AnimalEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/AnimalEntity.java @@ -26,11 +26,17 @@ package org.geysermc.geyser.entity.type.living.animal; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.entity.type.living.AgeableEntity; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; +import javax.annotation.Nonnull; import java.util.UUID; public class AnimalEntity extends AgeableEntity { @@ -39,6 +45,12 @@ public class AnimalEntity extends AgeableEntity { super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } + public final boolean canEat(GeyserItemStack itemStack) { + ItemMapping mapping = itemStack.getMapping(session); + String handIdentifier = mapping.getJavaIdentifier(); + return canEat(handIdentifier.replace("minecraft:", ""), mapping); + } + /** * @param javaIdentifierStripped the stripped Java identifier of the item that is potential breeding food. For example, * wheat. @@ -48,4 +60,28 @@ public class AnimalEntity extends AgeableEntity { // This is what it defaults to. OK. return javaIdentifierStripped.equals("wheat"); } + + @Nonnull + @Override + protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) { + if (canEat(itemInHand)) { + return InteractiveTag.FEED; + } + return super.testMobInteraction(itemInHand); + } + + @Nonnull + @Override + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + if (canEat(itemInHand)) { + // FEED + if (getFlag(EntityFlag.BABY)) { + playEntityEvent(EntityEventType.BABY_ANIMAL_FEED); + return InteractionResult.SUCCESS; + } else { + return InteractionResult.CONSUME; + } + } + return super.mobInteract(itemInHand); + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/AxolotlEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/AxolotlEntity.java index 2ada1fe09..9f7e17194 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/AxolotlEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/AxolotlEntity.java @@ -31,9 +31,13 @@ import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.util.EntityUtils; +import org.geysermc.geyser.util.InteractionResult; +import javax.annotation.Nonnull; import java.util.UUID; public class AxolotlEntity extends AnimalEntity { @@ -63,4 +67,19 @@ public class AxolotlEntity extends AnimalEntity { protected int getMaxAir() { return 6000; } + + @Override + protected boolean canBeLeashed() { + return true; + } + + @Nonnull + @Override + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + if (EntityUtils.attemptToBucket(session, this, itemInHand)) { + return InteractionResult.SUCCESS; + } else { + return super.mobInteract(itemInHand); + } + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/CowEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/CowEntity.java new file mode 100644 index 000000000..b5ae48b23 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/CowEntity.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2019-2022 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.entity.type.living.animal; + +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.SoundEvent; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.inventory.GeyserItemStack; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; + +import javax.annotation.Nonnull; +import java.util.UUID; + +public class CowEntity extends AnimalEntity { + public CowEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } + + @Nonnull + @Override + protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) { + if (getFlag(EntityFlag.BABY) || !itemInHand.getMapping(session).getJavaIdentifier().equals("minecraft:bucket")) { + return super.testMobInteraction(itemInHand); + } + + return InteractiveTag.MILK; + } + + @Nonnull + @Override + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + if (getFlag(EntityFlag.BABY) || !itemInHand.getMapping(session).getJavaIdentifier().equals("minecraft:bucket")) { + return super.mobInteract(itemInHand); + } + + session.playSoundEvent(SoundEvent.MILK, position); + return InteractionResult.SUCCESS; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/GoatEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/GoatEntity.java index 7442a5417..817b466fa 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/GoatEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/GoatEntity.java @@ -28,17 +28,20 @@ package org.geysermc.geyser.entity.type.living.animal; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose; import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata; import com.nukkitx.math.vector.Vector3f; -import lombok.Getter; +import com.nukkitx.protocol.bedrock.data.SoundEvent; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.InteractionResult; +import javax.annotation.Nonnull; import java.util.UUID; public class GoatEntity extends AnimalEntity { private static final float LONG_JUMPING_HEIGHT = 1.3f * 0.7f; private static final float LONG_JUMPING_WIDTH = 0.9f * 0.7f; - @Getter private boolean isScreamer; public GoatEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { @@ -59,4 +62,15 @@ public class GoatEntity extends AnimalEntity { super.setDimensions(pose); } } + + @Nonnull + @Override + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + if (!getFlag(EntityFlag.BABY) && itemInHand.getMapping(session).getJavaIdentifier().equals("minecraft:bucket")) { + session.playSoundEvent(isScreamer ? SoundEvent.MILK_SCREAMER : SoundEvent.MILK, position); + return InteractionResult.SUCCESS; + } else { + return super.mobInteract(itemInHand); + } + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/HoglinEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/HoglinEntity.java index e96124250..362c25256 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/HoglinEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/HoglinEntity.java @@ -56,4 +56,14 @@ public class HoglinEntity extends AnimalEntity { public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) { return javaIdentifierStripped.equals("crimson_fungus"); } + + @Override + protected boolean canBeLeashed() { + return isNotLeashed(); + } + + @Override + protected boolean isEnemy() { + return true; + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/MooshroomEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/MooshroomEntity.java index e75d20f8d..c249663ac 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/MooshroomEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/MooshroomEntity.java @@ -25,15 +25,62 @@ package org.geysermc.geyser.entity.type.living.animal; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ObjectEntityMetadata; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.inventory.GeyserItemStack; +import org.geysermc.geyser.inventory.item.StoredItemMappings; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; +import javax.annotation.Nonnull; import java.util.UUID; public class MooshroomEntity extends AnimalEntity { + private boolean isBrown = false; public MooshroomEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } + + public void setVariant(ObjectEntityMetadata entityMetadata) { + isBrown = entityMetadata.getValue().equals("brown"); + dirtyMetadata.put(EntityData.VARIANT, isBrown ? 1 : 0); + } + + @Nonnull + @Override + protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) { + StoredItemMappings storedItems = session.getItemMappings().getStoredItems(); + if (!isBaby()) { + if (itemInHand.getJavaId() == storedItems.bowl()) { + // Stew + return InteractiveTag.MOOSHROOM_MILK_STEW; + } else if (isAlive() && itemInHand.getJavaId() == storedItems.shears()) { + // Shear items + return InteractiveTag.MOOSHROOM_SHEAR; + } + } + return super.testMobInteraction(itemInHand); + } + + @Nonnull + @Override + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + StoredItemMappings storedItems = session.getItemMappings().getStoredItems(); + boolean isBaby = isBaby(); + if (!isBaby && itemInHand.getJavaId() == storedItems.bowl()) { + // Stew + return InteractionResult.SUCCESS; + } else if (!isBaby && isAlive() && itemInHand.getJavaId() == storedItems.shears()) { + // Shear items + return InteractionResult.SUCCESS; + } else if (isBrown && session.getTagCache().isSmallFlower(itemInHand) && itemInHand.getMapping(session).isHasSuspiciousStewEffect()) { + // ? + return InteractionResult.SUCCESS; + } + return super.mobInteract(itemInHand); + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/OcelotEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/OcelotEntity.java index ab7e9a053..4ed2bdce1 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/OcelotEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/OcelotEntity.java @@ -26,10 +26,15 @@ package org.geysermc.geyser.entity.type.living.animal; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; +import javax.annotation.Nonnull; import java.util.UUID; public class OcelotEntity extends AnimalEntity { @@ -42,4 +47,26 @@ public class OcelotEntity extends AnimalEntity { public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) { return javaIdentifierStripped.equals("cod") || javaIdentifierStripped.equals("salmon"); } + + @Nonnull + @Override + protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) { + if (!getFlag(EntityFlag.TRUSTING) && canEat(itemInHand) && session.getPlayerEntity().getPosition().distanceSquared(position) < 9f) { + // Attempt to feed + return InteractiveTag.FEED; + } else { + return super.testMobInteraction(itemInHand); + } + } + + @Nonnull + @Override + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + if (!getFlag(EntityFlag.TRUSTING) && canEat(itemInHand) && session.getPlayerEntity().getPosition().distanceSquared(position) < 9f) { + // Attempt to feed + return InteractionResult.SUCCESS; + } else { + return super.mobInteract(itemInHand); + } + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PandaEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PandaEntity.java index bfe743bc1..d607f113b 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PandaEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PandaEntity.java @@ -33,14 +33,19 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.util.UUID; public class PandaEntity extends AnimalEntity { - private int mainGene; - private int hiddenGene; + private Gene mainGene = Gene.NORMAL; + private Gene hiddenGene = Gene.NORMAL; public PandaEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); @@ -61,12 +66,12 @@ public class PandaEntity extends AnimalEntity { } public void setMainGene(ByteEntityMetadata entityMetadata) { - mainGene = entityMetadata.getPrimitiveValue(); + mainGene = Gene.fromId(entityMetadata.getPrimitiveValue()); updateAppearance(); } public void setHiddenGene(ByteEntityMetadata entityMetadata) { - hiddenGene = entityMetadata.getPrimitiveValue(); + hiddenGene = Gene.fromId(entityMetadata.getPrimitiveValue()); updateAppearance(); } @@ -86,23 +91,81 @@ public class PandaEntity extends AnimalEntity { return javaIdentifierStripped.equals("bamboo"); } + @Nonnull + @Override + protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) { + if (mainGene == Gene.WORRIED && session.isThunder()) { + return InteractiveTag.NONE; + } + return super.testMobInteraction(itemInHand); + } + + @Nonnull + @Override + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + if (mainGene == Gene.WORRIED && session.isThunder()) { + // Huh! + return InteractionResult.PASS; + } else if (getFlag(EntityFlag.LAYING_DOWN)) { + // Stop the panda from laying down + // TODO laying up is client-side? + return InteractionResult.SUCCESS; + } else if (canEat(itemInHand)) { + if (getFlag(EntityFlag.BABY)) { + playEntityEvent(EntityEventType.BABY_ANIMAL_FEED); + } + return InteractionResult.SUCCESS; + } + return InteractionResult.PASS; + } + + @Override + protected boolean canBeLeashed() { + return false; + } + /** * Update the panda's appearance, and take into consideration the recessive brown and weak traits that only show up * when both main and hidden genes match */ private void updateAppearance() { - if (mainGene == 4 || mainGene == 5) { - // Main gene is a recessive trait + if (mainGene.isRecessive) { if (mainGene == hiddenGene) { // Main and hidden genes match; this is what the panda looks like. - dirtyMetadata.put(EntityData.VARIANT, mainGene); + dirtyMetadata.put(EntityData.VARIANT, mainGene.ordinal()); } else { // Genes have no effect on appearance - dirtyMetadata.put(EntityData.VARIANT, 0); + dirtyMetadata.put(EntityData.VARIANT, Gene.NORMAL.ordinal()); } } else { // No need to worry about hidden gene - dirtyMetadata.put(EntityData.VARIANT, mainGene); + dirtyMetadata.put(EntityData.VARIANT, mainGene.ordinal()); + } + } + + enum Gene { + NORMAL(false), + LAZY(false), + WORRIED(false), + PLAYFUL(false), + BROWN(true), + WEAK(true), + AGGRESSIVE(false); + + private static final Gene[] VALUES = values(); + + private final boolean isRecessive; + + Gene(boolean isRecessive) { + this.isRecessive = isRecessive; + } + + @Nullable + private static Gene fromId(int id) { + if (id < 0 || id >= VALUES.length) { + return NORMAL; + } + return VALUES[id]; } } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PigEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PigEntity.java index a97193358..05f628f44 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PigEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PigEntity.java @@ -26,10 +26,16 @@ package org.geysermc.geyser.entity.type.living.animal; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.util.EntityUtils; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; +import javax.annotation.Nonnull; import java.util.UUID; public class PigEntity extends AnimalEntity { @@ -42,4 +48,37 @@ public class PigEntity extends AnimalEntity { public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) { return javaIdentifierStripped.equals("carrot") || javaIdentifierStripped.equals("potato") || javaIdentifierStripped.equals("beetroot"); } + + @Nonnull + @Override + protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) { + if (!canEat(itemInHand) && getFlag(EntityFlag.SADDLED) && passengers.isEmpty() && !session.isSneaking()) { + // Mount + return InteractiveTag.MOUNT; + } else { + InteractiveTag superTag = super.testMobInteraction(itemInHand); + if (superTag != InteractiveTag.NONE) { + return superTag; + } else { + return EntityUtils.attemptToSaddle(session, this, itemInHand).consumesAction() + ? InteractiveTag.SADDLE : InteractiveTag.NONE; + } + } + } + + @Nonnull + @Override + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + if (!canEat(itemInHand) && getFlag(EntityFlag.SADDLED) && passengers.isEmpty() && !session.isSneaking()) { + // Mount + return InteractionResult.SUCCESS; + } else { + InteractionResult superResult = super.mobInteract(itemInHand); + if (superResult.consumesAction()) { + return superResult; + } else { + return EntityUtils.attemptToSaddle(session, this, itemInHand); + } + } + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/SheepEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/SheepEntity.java index 284b4aea4..74e2ed368 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/SheepEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/SheepEntity.java @@ -30,19 +30,69 @@ import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; +import org.geysermc.geyser.util.ItemUtils; +import javax.annotation.Nonnull; import java.util.UUID; public class SheepEntity extends AnimalEntity { + private int color; public SheepEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } public void setSheepFlags(ByteEntityMetadata entityMetadata) { - byte xd = ((ByteEntityMetadata) entityMetadata).getPrimitiveValue(); + byte xd = entityMetadata.getPrimitiveValue(); setFlag(EntityFlag.SHEARED, (xd & 0x10) == 0x10); - dirtyMetadata.put(EntityData.COLOR, xd); + color = xd & 15; + dirtyMetadata.put(EntityData.COLOR, (byte) color); + } + + @Nonnull + @Override + protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) { + if (itemInHand.getJavaId() == session.getItemMappings().getStoredItems().shears()) { + return InteractiveTag.SHEAR; + } else { + InteractiveTag tag = super.testMobInteraction(itemInHand); + if (tag != InteractiveTag.NONE) { + return tag; + } else { + int color = ItemUtils.getDyeColor(itemInHand.getJavaId()); + if (canDye(color)) { + return InteractiveTag.DYE; + } + return InteractiveTag.NONE; + } + } + } + + @Nonnull + @Override + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + if (itemInHand.getJavaId() == session.getItemMappings().getStoredItems().shears()) { + return InteractionResult.CONSUME; + } else { + InteractionResult superResult = super.mobInteract(itemInHand); + if (superResult.consumesAction()) { + return superResult; + } else { + int color = ItemUtils.getDyeColor(itemInHand.getJavaId()); + if (canDye(color)) { + // Dyeing the sheep + return InteractionResult.SUCCESS; + } + return InteractionResult.PASS; + } + } + } + + private boolean canDye(int color) { + return color != -1 && color != this.color && !getFlag(EntityFlag.SHEARED); } } \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/StriderEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/StriderEntity.java index 27438544c..5f42b4b67 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/StriderEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/StriderEntity.java @@ -30,9 +30,14 @@ import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.util.EntityUtils; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; +import javax.annotation.Nonnull; import java.util.UUID; public class StriderEntity extends AnimalEntity { @@ -90,4 +95,37 @@ public class StriderEntity extends AnimalEntity { public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) { return javaIdentifierStripped.equals("warped_fungus"); } + + @Nonnull + @Override + protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) { + if (!canEat(itemInHand) && getFlag(EntityFlag.SADDLED) && passengers.isEmpty() && !session.isSneaking()) { + // Mount Strider + return InteractiveTag.RIDE_STRIDER; + } else { + InteractiveTag tag = super.testMobInteraction(itemInHand); + if (tag != InteractiveTag.NONE) { + return tag; + } else { + return EntityUtils.attemptToSaddle(session, this, itemInHand).consumesAction() + ? InteractiveTag.SADDLE : InteractiveTag.NONE; + } + } + } + + @Nonnull + @Override + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + if (!canEat(itemInHand) && getFlag(EntityFlag.SADDLED) && passengers.isEmpty() && !session.isSneaking()) { + // Mount Strider + return InteractionResult.SUCCESS; + } else { + InteractionResult superResult = super.mobInteract(itemInHand); + if (superResult.consumesAction()) { + return superResult; + } else { + return EntityUtils.attemptToSaddle(session, this, itemInHand); + } + } + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/TurtleEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/TurtleEntity.java index f7d987300..79a7b8f50 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/TurtleEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/TurtleEntity.java @@ -52,4 +52,9 @@ public class TurtleEntity extends AnimalEntity { public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) { return javaIdentifierStripped.equals("seagrass"); } + + @Override + protected boolean canBeLeashed() { + return false; + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/AbstractHorseEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/AbstractHorseEntity.java index ef53f604f..de26e380e 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/AbstractHorseEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/AbstractHorseEntity.java @@ -37,9 +37,13 @@ import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket; import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.entity.attribute.GeyserAttributeType; import org.geysermc.geyser.entity.type.living.animal.AnimalEntity; -import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; +import javax.annotation.Nonnull; import java.util.Set; import java.util.UUID; @@ -122,4 +126,154 @@ public class AbstractHorseEntity extends AnimalEntity { public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) { return DONKEY_AND_HORSE_FOODS.contains(javaIdentifierStripped); } + + @Nonnull + @Override + protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) { + boolean isBaby = isBaby(); + if (!isBaby) { + if (getFlag(EntityFlag.TAMED) && session.isSneaking()) { + return InteractiveTag.OPEN_CONTAINER; + } + + if (!passengers.isEmpty()) { + return super.testMobInteraction(itemInHand); + } + } + + if (!itemInHand.isEmpty()) { + if (canEat(itemInHand)) { + return InteractiveTag.FEED; + } + + if (testSaddle(itemInHand)) { + return InteractiveTag.SADDLE; + } + + if (!getFlag(EntityFlag.TAMED)) { + // Horse will become mad + return InteractiveTag.NONE; + } + + if (testForChest(itemInHand)) { + return InteractiveTag.ATTACH_CHEST; + } + + if (additionalTestForInventoryOpen(itemInHand) || !isBaby && !getFlag(EntityFlag.SADDLED) && itemInHand.getJavaId() == session.getItemMappings().getStoredItems().saddle()) { + // Will open the inventory to be saddled + return InteractiveTag.OPEN_CONTAINER; + } + } + + if (isBaby) { + return super.testMobInteraction(itemInHand); + } else { + return InteractiveTag.MOUNT; + } + } + + @Nonnull + @Override + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + boolean isBaby = isBaby(); + if (!isBaby) { + if (getFlag(EntityFlag.TAMED) && session.isSneaking()) { + // Will open the inventory + return InteractionResult.SUCCESS; + } + + if (!passengers.isEmpty()) { + return super.mobInteract(itemInHand); + } + } + + if (!itemInHand.isEmpty()) { + if (canEat(itemInHand)) { + if (isBaby) { + playEntityEvent(EntityEventType.BABY_ANIMAL_FEED); + } + return InteractionResult.CONSUME; + } + + if (testSaddle(itemInHand)) { + return InteractionResult.SUCCESS; + } + + if (!getFlag(EntityFlag.TAMED)) { + // Horse will become mad + return InteractionResult.SUCCESS; + } + + if (testForChest(itemInHand)) { + // TODO looks like chest is also handled client side + return InteractionResult.SUCCESS; + } + + // Note: yes, this code triggers for llamas too. lol (as of Java Edition 1.18.1) + if (additionalTestForInventoryOpen(itemInHand) || (!isBaby && !getFlag(EntityFlag.SADDLED) && itemInHand.getJavaId() == session.getItemMappings().getStoredItems().saddle())) { + // Will open the inventory to be saddled + return InteractionResult.SUCCESS; + } + } + + if (isBaby) { + return super.mobInteract(itemInHand); + } else { + // Attempt to mount + // TODO client-set flags sitting standing? + return InteractionResult.SUCCESS; + } + } + + protected boolean testSaddle(@Nonnull GeyserItemStack itemInHand) { + return isAlive() && !getFlag(EntityFlag.BABY) && getFlag(EntityFlag.TAMED); + } + + protected boolean testForChest(@Nonnull GeyserItemStack itemInHand) { + return false; + } + + protected boolean additionalTestForInventoryOpen(@Nonnull GeyserItemStack itemInHand) { + return itemInHand.getMapping(session).getJavaIdentifier().endsWith("_horse_armor"); + } + + /* Just a place to stuff common code for the undead variants without having duplicate code */ + + protected final InteractiveTag testUndeadHorseInteraction(@Nonnull GeyserItemStack itemInHand) { + if (!getFlag(EntityFlag.TAMED)) { + return InteractiveTag.NONE; + } else if (isBaby()) { + return testMobInteraction(itemInHand); + } else if (session.isSneaking()) { + return InteractiveTag.OPEN_CONTAINER; + } else if (!passengers.isEmpty()) { + return testMobInteraction(itemInHand); + } else { + if (session.getItemMappings().getStoredItems().saddle() == itemInHand.getJavaId()) { + return InteractiveTag.OPEN_CONTAINER; + } + + if (testSaddle(itemInHand)) { + return InteractiveTag.SADDLE; + } + + return InteractiveTag.RIDE_HORSE; + } + } + + protected final InteractionResult undeadHorseInteract(@Nonnull GeyserItemStack itemInHand) { + if (!getFlag(EntityFlag.TAMED)) { + return InteractionResult.PASS; + } else if (isBaby()) { + return mobInteract(itemInHand); + } else if (session.isSneaking()) { + // Opens inventory + return InteractionResult.SUCCESS; + } else if (!passengers.isEmpty()) { + return mobInteract(itemInHand); + } else { + // The client tests for saddle but it doesn't matter for us at this point. + return InteractionResult.SUCCESS; + } + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/ChestedHorseEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/ChestedHorseEntity.java index fb907829a..7d59be713 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/ChestedHorseEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/ChestedHorseEntity.java @@ -26,9 +26,12 @@ package org.geysermc.geyser.entity.type.living.animal.horse; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.session.GeyserSession; +import javax.annotation.Nonnull; import java.util.UUID; public class ChestedHorseEntity extends AbstractHorseEntity { @@ -41,4 +44,21 @@ public class ChestedHorseEntity extends AbstractHorseEntity { protected int getContainerBaseSize() { return 16; } + + @Override + protected boolean testSaddle(@Nonnull GeyserItemStack itemInHand) { + // Not checked here + return false; + } + + @Override + protected boolean testForChest(@Nonnull GeyserItemStack itemInHand) { + return itemInHand.getJavaId() == session.getItemMappings().getStoredItems().chest() && !getFlag(EntityFlag.CHESTED); + } + + @Override + protected boolean additionalTestForInventoryOpen(@Nonnull GeyserItemStack itemInHand) { + // Armor won't work on these + return false; + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/LlamaEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/LlamaEntity.java index 41ed74f5a..c2548daaf 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/LlamaEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/LlamaEntity.java @@ -31,8 +31,8 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.packet.MobArmorEquipmentPacket; import org.geysermc.geyser.entity.EntityDefinition; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.session.GeyserSession; import java.util.UUID; diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/SkeletonHorseEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/SkeletonHorseEntity.java new file mode 100644 index 000000000..c9f95f507 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/SkeletonHorseEntity.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2019-2022 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.entity.type.living.animal.horse; + +import com.nukkitx.math.vector.Vector3f; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.inventory.GeyserItemStack; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; + +import javax.annotation.Nonnull; +import java.util.UUID; + +public class SkeletonHorseEntity extends AbstractHorseEntity { + public SkeletonHorseEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } + + @Nonnull + @Override + protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) { + return testUndeadHorseInteraction(itemInHand); + } + + @Nonnull + @Override + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + return undeadHorseInteract(itemInHand); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/ZombieHorseEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/ZombieHorseEntity.java new file mode 100644 index 000000000..ddde11c5d --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/ZombieHorseEntity.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2019-2022 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.entity.type.living.animal.horse; + +import com.nukkitx.math.vector.Vector3f; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.inventory.GeyserItemStack; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; + +import javax.annotation.Nonnull; +import java.util.UUID; + +public class ZombieHorseEntity extends AbstractHorseEntity { + public ZombieHorseEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } + + @Nonnull + @Override + protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) { + return testUndeadHorseInteraction(itemInHand); + } + + @Nonnull + @Override + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + return undeadHorseInteract(itemInHand); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/CatEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/CatEntity.java index c38b15397..c17503606 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/CatEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/CatEntity.java @@ -32,9 +32,13 @@ import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; +import javax.annotation.Nonnull; import java.util.UUID; public class CatEntity extends TameableEntity { @@ -98,4 +102,28 @@ public class CatEntity extends TameableEntity { public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) { return javaIdentifierStripped.equals("cod") || javaIdentifierStripped.equals("salmon"); } + + @Nonnull + @Override + protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) { + boolean tamed = getFlag(EntityFlag.TAMED); + if (tamed && ownerBedrockId == session.getPlayerEntity().getGeyserId()) { + // Toggle sitting + return getFlag(EntityFlag.SITTING) ? InteractiveTag.STAND : InteractiveTag.SIT; + } else { + return !canEat(itemInHand) || health >= maxHealth && tamed ? InteractiveTag.NONE : InteractiveTag.FEED; + } + } + + @Nonnull + @Override + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + boolean tamed = getFlag(EntityFlag.TAMED); + if (tamed && ownerBedrockId == session.getPlayerEntity().getGeyserId()) { + return InteractionResult.SUCCESS; + } else { + // Attempt to feed + return !canEat(itemInHand) || health >= maxHealth && tamed ? InteractionResult.PASS : InteractionResult.SUCCESS; + } + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/ParrotEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/ParrotEntity.java index 23f7696d4..b7aca99e5 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/ParrotEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/ParrotEntity.java @@ -26,10 +26,15 @@ package org.geysermc.geyser.entity.type.living.animal.tameable; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; +import javax.annotation.Nonnull; import java.util.UUID; public class ParrotEntity extends TameableEntity { @@ -40,6 +45,46 @@ public class ParrotEntity extends TameableEntity { @Override public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) { - return javaIdentifierStripped.contains("seeds") || javaIdentifierStripped.equals("cookie"); + return false; + } + + private boolean isTameFood(String javaIdentifierStripped) { + return javaIdentifierStripped.contains("seeds"); + } + + private boolean isPoisonousFood(String javaIdentifierStripped) { + return javaIdentifierStripped.equals("cookie"); + } + + @Nonnull + @Override + protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) { + String javaIdentifierStripped = itemInHand.getMapping(session).getJavaIdentifier().replace("minecraft:", ""); + boolean tame = getFlag(EntityFlag.TAMED); + if (!tame && isTameFood(javaIdentifierStripped)) { + return InteractiveTag.FEED; + } else if (isPoisonousFood(javaIdentifierStripped)) { + return InteractiveTag.FEED; + } else if (onGround && tame && ownerBedrockId == session.getPlayerEntity().getGeyserId()) { + // Sitting/standing + return getFlag(EntityFlag.SITTING) ? InteractiveTag.STAND : InteractiveTag.SIT; + } + return super.testMobInteraction(itemInHand); + } + + @Nonnull + @Override + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + String javaIdentifierStripped = itemInHand.getMapping(session).getJavaIdentifier().replace("minecraft:", ""); + boolean tame = getFlag(EntityFlag.TAMED); + if (!tame && isTameFood(javaIdentifierStripped)) { + return InteractionResult.SUCCESS; + } else if (isPoisonousFood(javaIdentifierStripped)) { + return InteractionResult.SUCCESS; + } else if (onGround && tame && ownerBedrockId == session.getPlayerEntity().getGeyserId()) { + // Sitting/standing + return InteractionResult.SUCCESS; + } + return super.mobInteract(itemInHand); } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/TameableEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/TameableEntity.java index 9bdb57368..50d17eaaa 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/TameableEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/TameableEntity.java @@ -64,14 +64,21 @@ public class TameableEntity extends AnimalEntity { Entity entity = session.getEntityCache().getPlayerEntity(entityMetadata.getValue().get()); // Used as both a check since the player isn't in the entity cache and a normal fallback if (entity == null) { - entity = session.getPlayerEntity(); + // Set to tame, but indicate that we are not the player that owns this + ownerBedrockId = Long.MAX_VALUE; + } else { + // Translate to entity ID + ownerBedrockId = entity.getGeyserId(); } - // Translate to entity ID - ownerBedrockId = entity.getGeyserId(); } else { // Reset ownerBedrockId = 0L; } dirtyMetadata.put(EntityData.OWNER_EID, ownerBedrockId); } + + @Override + protected boolean canBeLeashed() { + return isNotLeashed(); + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/WolfEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/WolfEntity.java index 60a4a1993..b14b40dc3 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/WolfEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/WolfEntity.java @@ -32,9 +32,14 @@ import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; +import org.geysermc.geyser.util.ItemUtils; +import javax.annotation.Nonnull; import java.util.Set; import java.util.UUID; @@ -90,4 +95,45 @@ public class WolfEntity extends TameableEntity { // Cannot be a baby to eat these foods return WOLF_FOODS.contains(javaIdentifierStripped) && !isBaby(); } + + @Override + protected boolean canBeLeashed() { + return !getFlag(EntityFlag.ANGRY) && super.canBeLeashed(); + } + + @Nonnull + @Override + protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) { + if (getFlag(EntityFlag.ANGRY)) { + return InteractiveTag.NONE; + } + if (itemInHand.getMapping(session).getJavaIdentifier().equals("minecraft:bone") && !getFlag(EntityFlag.TAMED)) { + // Bone and untamed - can tame + return InteractiveTag.TAME; + } else { + int color = ItemUtils.getDyeColor(itemInHand.getJavaId()); + if (color != -1) { + // If this fails, as of Java Edition 1.18.1, you cannot toggle sit/stand + if (color != this.collarColor) { + return InteractiveTag.DYE; + } + } else if (getFlag(EntityFlag.TAMED) && ownerBedrockId == session.getPlayerEntity().getGeyserId()) { + // Tamed and owned by player - can sit/stand + return getFlag(EntityFlag.SITTING) ? InteractiveTag.STAND : InteractiveTag.SIT; + } + } + return super.testMobInteraction(itemInHand); + } + + @Nonnull + @Override + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + if (ownerBedrockId == session.getPlayerEntity().getGeyserId() || getFlag(EntityFlag.TAMED) + || itemInHand.getMapping(session).getJavaIdentifier().equals("minecraft:bone") && !getFlag(EntityFlag.ANGRY)) { + // Sitting toggle or feeding; not angry + return InteractionResult.CONSUME; + } else { + return InteractionResult.PASS; + } + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/merchant/AbstractMerchantEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/merchant/AbstractMerchantEntity.java index 28a523f40..633ba707f 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/merchant/AbstractMerchantEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/merchant/AbstractMerchantEntity.java @@ -26,10 +26,16 @@ package org.geysermc.geyser.entity.type.living.merchant; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.type.living.AgeableEntity; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; +import javax.annotation.Nonnull; import java.util.UUID; public class AbstractMerchantEntity extends AgeableEntity { @@ -37,4 +43,37 @@ public class AbstractMerchantEntity extends AgeableEntity { public AbstractMerchantEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } + + @Override + protected boolean canBeLeashed() { + return false; + } + + @Nonnull + @Override + protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) { + String javaIdentifier = itemInHand.getMapping(session).getJavaIdentifier(); + if (!javaIdentifier.equals("minecraft:villager_spawn_egg") + && (definition != EntityDefinitions.VILLAGER || !getFlag(EntityFlag.SLEEPING) && ((VillagerEntity) this).isCanTradeWith())) { + // An additional check we know cannot work + if (!isBaby()) { + return InteractiveTag.TRADE; + } + } + return super.testMobInteraction(itemInHand); + } + + @Nonnull + @Override + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + String javaIdentifier = itemInHand.getMapping(session).getJavaIdentifier(); + if (!javaIdentifier.equals("minecraft:villager_spawn_egg") + && (definition != EntityDefinitions.VILLAGER || !getFlag(EntityFlag.SLEEPING)) + && (definition != EntityDefinitions.WANDERING_TRADER || !getFlag(EntityFlag.BABY))) { + // Trading time + return InteractionResult.SUCCESS; + } else { + return super.mobInteract(itemInHand); + } + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/merchant/VillagerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/merchant/VillagerEntity.java index 0f90e4d38..866ba36fc 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/merchant/VillagerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/merchant/VillagerEntity.java @@ -33,52 +33,49 @@ import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket; -import it.unimi.dsi.fastutil.ints.Int2IntMap; -import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import lombok.Getter; import org.geysermc.geyser.entity.EntityDefinition; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.registry.BlockRegistries; +import org.geysermc.geyser.session.GeyserSession; import java.util.Optional; import java.util.UUID; public class VillagerEntity extends AbstractMerchantEntity { - /** * A map of Java profession IDs to Bedrock IDs */ - public static final Int2IntMap VILLAGER_PROFESSIONS = new Int2IntOpenHashMap(); + private static final int[] VILLAGER_PROFESSIONS = new int[15]; /** * A map of all Java region IDs (plains, savanna...) to Bedrock */ - public static final Int2IntMap VILLAGER_REGIONS = new Int2IntOpenHashMap(); + private static final int[] VILLAGER_REGIONS = new int[7]; static { // Java villager profession IDs -> Bedrock - VILLAGER_PROFESSIONS.put(0, 0); - VILLAGER_PROFESSIONS.put(1, 8); - VILLAGER_PROFESSIONS.put(2, 11); - VILLAGER_PROFESSIONS.put(3, 6); - VILLAGER_PROFESSIONS.put(4, 7); - VILLAGER_PROFESSIONS.put(5, 1); - VILLAGER_PROFESSIONS.put(6, 2); - VILLAGER_PROFESSIONS.put(7, 4); - VILLAGER_PROFESSIONS.put(8, 12); - VILLAGER_PROFESSIONS.put(9, 5); - VILLAGER_PROFESSIONS.put(10, 13); - VILLAGER_PROFESSIONS.put(11, 14); - VILLAGER_PROFESSIONS.put(12, 3); - VILLAGER_PROFESSIONS.put(13, 10); - VILLAGER_PROFESSIONS.put(14, 9); + VILLAGER_PROFESSIONS[0] = 0; + VILLAGER_PROFESSIONS[1] = 8; + VILLAGER_PROFESSIONS[2] = 11; + VILLAGER_PROFESSIONS[3] = 6; + VILLAGER_PROFESSIONS[4] = 7; + VILLAGER_PROFESSIONS[5] = 1; + VILLAGER_PROFESSIONS[6] = 2; + VILLAGER_PROFESSIONS[7] = 4; + VILLAGER_PROFESSIONS[8] = 12; + VILLAGER_PROFESSIONS[9] = 5; + VILLAGER_PROFESSIONS[10] = 13; + VILLAGER_PROFESSIONS[11] = 14; + VILLAGER_PROFESSIONS[12] = 3; + VILLAGER_PROFESSIONS[13] = 10; + VILLAGER_PROFESSIONS[14] = 9; - VILLAGER_REGIONS.put(0, 1); - VILLAGER_REGIONS.put(1, 2); - VILLAGER_REGIONS.put(2, 0); - VILLAGER_REGIONS.put(3, 3); - VILLAGER_REGIONS.put(4, 4); - VILLAGER_REGIONS.put(5, 5); - VILLAGER_REGIONS.put(6, 6); + VILLAGER_REGIONS[0] = 1; + VILLAGER_REGIONS[1] = 2; + VILLAGER_REGIONS[2] = 0; + VILLAGER_REGIONS[3] = 3; + VILLAGER_REGIONS[4] = 4; + VILLAGER_REGIONS[5] = 5; + VILLAGER_REGIONS[6] = 6; } private Vector3i bedPosition; @@ -95,12 +92,12 @@ public class VillagerEntity extends AbstractMerchantEntity { public void setVillagerData(EntityMetadata entityMetadata) { VillagerData villagerData = entityMetadata.getValue(); // Profession - int profession = VILLAGER_PROFESSIONS.get(villagerData.getProfession()); + int profession = getBedrockProfession(villagerData.getProfession()); canTradeWith = profession != 14 && profession != 0; // Not a notwit and not professionless dirtyMetadata.put(EntityData.VARIANT, profession); //metadata.put(EntityData.SKIN_ID, villagerData.getType()); Looks like this is modified but for any reason? // Region - dirtyMetadata.put(EntityData.MARK_VARIANT, VILLAGER_REGIONS.get(villagerData.getType())); + dirtyMetadata.put(EntityData.MARK_VARIANT, getBedrockRegion(villagerData.getType())); // Trade tier - different indexing in Bedrock dirtyMetadata.put(EntityData.TRADE_TIER, villagerData.getLevel() - 1); } @@ -158,4 +155,12 @@ public class VillagerEntity extends AbstractMerchantEntity { moveEntityPacket.setTeleported(false); session.sendUpstreamPacket(moveEntityPacket); } + + public static int getBedrockProfession(int javaProfession) { + return javaProfession >= 0 && javaProfession < VILLAGER_PROFESSIONS.length ? VILLAGER_PROFESSIONS[javaProfession] : 0; + } + + public static int getBedrockRegion(int javaRegion) { + return javaRegion >= 0 && javaRegion < VILLAGER_REGIONS.length ? VILLAGER_REGIONS[javaRegion] : 0; + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/CreeperEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/CreeperEntity.java index 12117d949..cf9393410 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/CreeperEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/CreeperEntity.java @@ -28,10 +28,15 @@ package org.geysermc.geyser.entity.type.living.monster; import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata; import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.SoundEvent; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; +import javax.annotation.Nonnull; import java.util.UUID; public class CreeperEntity extends MonsterEntity { @@ -55,4 +60,26 @@ public class CreeperEntity extends MonsterEntity { ignitedByFlintAndSteel = entityMetadata.getPrimitiveValue(); setFlag(EntityFlag.IGNITED, ignitedByFlintAndSteel); } + + @Nonnull + @Override + protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) { + if (itemInHand.getJavaId() == session.getItemMappings().getStoredItems().flintAndSteel()) { + return InteractiveTag.IGNITE_CREEPER; + } else { + return super.testMobInteraction(itemInHand); + } + } + + @Nonnull + @Override + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + if (itemInHand.getJavaId() == session.getItemMappings().getStoredItems().flintAndSteel()) { + // Ignite creeper + session.playSoundEvent(SoundEvent.IGNITE, position); + return InteractionResult.SUCCESS; + } else { + return super.mobInteract(itemInHand); + } + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonEntity.java index de1dab463..0069bfb5b 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonEntity.java @@ -150,6 +150,11 @@ public class EnderDragonEntity extends MobEntity implements Tickable { return super.despawnEntity(); } + @Override + protected boolean isEnemy() { + return true; + } + @Override public void tick() { effectTick(); @@ -288,10 +293,6 @@ public class EnderDragonEntity extends MobEntity implements Tickable { session.sendUpstreamPacket(playSoundPacket); } - private boolean isAlive() { - return health > 0; - } - private boolean isHovering() { return phase == 10; } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/GhastEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/GhastEntity.java index 035d405a0..511c56ff7 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/GhastEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/GhastEntity.java @@ -44,4 +44,9 @@ public class GhastEntity extends FlyingEntity { // If the ghast is attacking dirtyMetadata.put(EntityData.CHARGE_AMOUNT, (byte) (entityMetadata.getPrimitiveValue() ? 1 : 0)); } + + @Override + protected boolean isEnemy() { + return true; + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/MonsterEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/MonsterEntity.java index 885961326..92fbeee67 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/MonsterEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/MonsterEntity.java @@ -37,4 +37,9 @@ public class MonsterEntity extends CreatureEntity { public MonsterEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } + + @Override + protected boolean isEnemy() { + return true; + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/PhantomEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/PhantomEntity.java index bdc461518..dff79104b 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/PhantomEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/PhantomEntity.java @@ -48,4 +48,9 @@ public class PhantomEntity extends FlyingEntity { setBoundingBoxHeight(boundsScale * definition.height()); dirtyMetadata.put(EntityData.SCALE, modelScale); } + + @Override + protected boolean isEnemy() { + return true; + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/PiglinEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/PiglinEntity.java index 8d1c54a00..f0577ee20 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/PiglinEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/PiglinEntity.java @@ -30,8 +30,12 @@ import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; +import javax.annotation.Nonnull; import java.util.UUID; public class PiglinEntity extends BasePiglinEntity { @@ -64,4 +68,30 @@ public class PiglinEntity extends BasePiglinEntity { super.updateOffHand(session); } + + @Nonnull + @Override + protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) { + InteractiveTag tag = super.testMobInteraction(itemInHand); + if (tag != InteractiveTag.NONE) { + return tag; + } else { + return canGiveGoldTo(itemInHand) ? InteractiveTag.BARTER : InteractiveTag.NONE; + } + } + + @Nonnull + @Override + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + InteractionResult superResult = super.mobInteract(itemInHand); + if (superResult.consumesAction()) { + return superResult; + } else { + return canGiveGoldTo(itemInHand) ? InteractionResult.SUCCESS : InteractionResult.PASS; + } + } + + private boolean canGiveGoldTo(@Nonnull GeyserItemStack itemInHand) { + return !getFlag(EntityFlag.BABY) && itemInHand.getJavaId() == session.getItemMappings().getStoredItems().goldIngot() && !getFlag(EntityFlag.ADMIRING); + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ShulkerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ShulkerEntity.java index 56719e902..ff1ba9ac3 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ShulkerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ShulkerEntity.java @@ -65,4 +65,9 @@ public class ShulkerEntity extends GolemEntity { dirtyMetadata.put(EntityData.VARIANT, Math.abs(color - 15)); } } + + @Override + protected boolean isEnemy() { + return true; + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ZoglinEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ZoglinEntity.java index f02031044..dd5acbfb1 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ZoglinEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ZoglinEntity.java @@ -55,4 +55,14 @@ public class ZoglinEntity extends MonsterEntity { float scale = getFlag(EntityFlag.BABY) ? 0.55f : 1f; return scale * definition.height(); } + + @Override + protected boolean canBeLeashed() { + return isNotLeashed(); + } + + @Override + protected boolean isEnemy() { + return true; + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ZombieVillagerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ZombieVillagerEntity.java index 15bcc9c6a..1ec0fc26b 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ZombieVillagerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ZombieVillagerEntity.java @@ -33,33 +33,56 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.entity.type.living.merchant.VillagerEntity; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InteractiveTag; +import javax.annotation.Nonnull; import java.util.UUID; public class ZombieVillagerEntity extends ZombieEntity { - private boolean isTransforming; public ZombieVillagerEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } public void setTransforming(BooleanEntityMetadata entityMetadata) { - isTransforming = entityMetadata.getPrimitiveValue(); - setFlag(EntityFlag.IS_TRANSFORMING, isTransforming); + setFlag(EntityFlag.IS_TRANSFORMING, entityMetadata.getPrimitiveValue()); setFlag(EntityFlag.SHAKING, isShaking()); } public void setZombieVillagerData(EntityMetadata entityMetadata) { VillagerData villagerData = entityMetadata.getValue(); - dirtyMetadata.put(EntityData.VARIANT, VillagerEntity.VILLAGER_PROFESSIONS.get(villagerData.getProfession())); // Actually works properly with the OptionalPack - dirtyMetadata.put(EntityData.MARK_VARIANT, VillagerEntity.VILLAGER_REGIONS.get(villagerData.getType())); + dirtyMetadata.put(EntityData.VARIANT, VillagerEntity.getBedrockProfession(villagerData.getProfession())); // Actually works properly with the OptionalPack + dirtyMetadata.put(EntityData.MARK_VARIANT, VillagerEntity.getBedrockRegion(villagerData.getType())); // Used with the OptionalPack dirtyMetadata.put(EntityData.TRADE_TIER, villagerData.getLevel() - 1); } @Override protected boolean isShaking() { - return isTransforming || super.isShaking(); + return getFlag(EntityFlag.IS_TRANSFORMING) || super.isShaking(); + } + + @Nonnull + @Override + protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) { + if (itemInHand.getJavaId() == session.getItemMappings().getStoredItems().goldenApple()) { + return InteractiveTag.CURE; + } else { + return super.testMobInteraction(itemInHand); + } + } + + @Nonnull + @Override + protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + if (itemInHand.getJavaId() == session.getItemMappings().getStoredItems().goldenApple()) { + // The client doesn't know if the entity has weakness as that's not usually sent over the network + return InteractionResult.CONSUME; + } else { + return super.mobInteract(itemInHand); + } } } diff --git a/core/src/main/java/org/geysermc/geyser/inventory/PlayerInventory.java b/core/src/main/java/org/geysermc/geyser/inventory/PlayerInventory.java index 14c796a5f..7b1064c8f 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/PlayerInventory.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/PlayerInventory.java @@ -25,6 +25,7 @@ package org.geysermc.geyser.inventory; +import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; import lombok.Getter; import lombok.Setter; import org.geysermc.geyser.GeyserImpl; @@ -61,6 +62,10 @@ public class PlayerInventory extends Inventory { cursor = newCursor; } + public GeyserItemStack getItemInHand(@Nonnull Hand hand) { + return hand == Hand.OFF_HAND ? getOffhand() : getItemInHand(); + } + public GeyserItemStack getItemInHand() { if (36 + heldItemSlot > this.size) { GeyserImpl.getInstance().getLogger().debug("Held item slot was larger than expected!"); diff --git a/core/src/main/java/org/geysermc/geyser/inventory/item/StoredItemMappings.java b/core/src/main/java/org/geysermc/geyser/inventory/item/StoredItemMappings.java index 2098e04a8..e4296c2d4 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/item/StoredItemMappings.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/item/StoredItemMappings.java @@ -41,16 +41,27 @@ public class StoredItemMappings { private final ItemMapping bamboo; private final ItemMapping banner; private final ItemMapping barrier; + private final int bowl; + private final int chest; private final ItemMapping compass; private final ItemMapping crossbow; private final ItemMapping enchantedBook; private final ItemMapping fishingRod; + private final int flintAndSteel; + private final int goldenApple; + private final int goldIngot; + private final int ironIngot; + private final int lead; private final ItemMapping lodestoneCompass; private final ItemMapping milkBucket; + private final int nameTag; private final ItemMapping powderSnowBucket; private final ItemMapping playerHead; private final ItemMapping egg; + private final int saddle; + private final int shears; private final ItemMapping shield; + private final int waterBucket; private final ItemMapping wheat; private final ItemMapping writableBook; @@ -58,16 +69,27 @@ public class StoredItemMappings { this.bamboo = load(itemMappings, "bamboo"); this.banner = load(itemMappings, "white_banner"); // As of 1.17.10, all banners have the same Bedrock ID this.barrier = load(itemMappings, "barrier"); + this.bowl = load(itemMappings, "bowl").getJavaId(); + this.chest = load(itemMappings, "chest").getJavaId(); this.compass = load(itemMappings, "compass"); this.crossbow = load(itemMappings, "crossbow"); this.enchantedBook = load(itemMappings, "enchanted_book"); this.fishingRod = load(itemMappings, "fishing_rod"); + this.flintAndSteel = load(itemMappings, "flint_and_steel").getJavaId(); + this.goldenApple = load(itemMappings, "golden_apple").getJavaId(); + this.goldIngot = load(itemMappings, "gold_ingot").getJavaId(); + this.ironIngot = load(itemMappings, "iron_ingot").getJavaId(); + this.lead = load(itemMappings, "lead").getJavaId(); this.lodestoneCompass = load(itemMappings, "lodestone_compass"); this.milkBucket = load(itemMappings, "milk_bucket"); + this.nameTag = load(itemMappings, "name_tag").getJavaId(); this.powderSnowBucket = load(itemMappings, "powder_snow_bucket"); this.playerHead = load(itemMappings, "player_head"); this.egg = load(itemMappings, "egg"); + this.saddle = load(itemMappings, "saddle").getJavaId(); + this.shears = load(itemMappings, "shears").getJavaId(); this.shield = load(itemMappings, "shield"); + this.waterBucket = load(itemMappings, "water_bucket").getJavaId(); this.wheat = load(itemMappings, "wheat"); this.writableBook = load(itemMappings, "writable_book"); } diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java index 209588d72..9614e9da8 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java @@ -38,10 +38,7 @@ import com.nukkitx.protocol.bedrock.packet.StartGamePacket; import com.nukkitx.protocol.bedrock.v471.Bedrock_v471; import com.nukkitx.protocol.bedrock.v475.Bedrock_v475; import com.nukkitx.protocol.bedrock.v486.Bedrock_v486; -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.ints.*; import it.unimi.dsi.fastutil.objects.*; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; @@ -49,6 +46,8 @@ import org.geysermc.geyser.inventory.item.StoredItemMappings; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.type.*; +import org.geysermc.geyser.util.ItemUtils; +import org.geysermc.geyser.util.collection.FixedInt2IntMap; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -84,6 +83,10 @@ public class ItemRegistryPopulator { throw new AssertionError("Unable to load Java runtime item IDs", e); } + // We can reduce some operations as Java information is the same across all palette versions + boolean firstMappingsPass = true; + Int2IntMap dyeColors = new FixedInt2IntMap(); + /* Load item palette */ for (Map.Entry palette : PALETTE_VERSIONS.entrySet()) { TypeReference> paletteEntriesType = new TypeReference<>() {}; @@ -369,7 +372,8 @@ public class ItemRegistryPopulator { .bedrockData(mappingItem.getBedrockData()) .bedrockBlockId(bedrockBlockId) .stackSize(stackSize) - .maxDamage(mappingItem.getMaxDamage()); + .maxDamage(mappingItem.getMaxDamage()) + .hasSuspiciousStewEffect(mappingItem.isHasSuspiciousStewEffect()); if (mappingItem.getRepairMaterials() != null) { mappingBuilder = mappingBuilder.repairMaterials(new ObjectOpenHashSet<>(mappingItem.getRepairMaterials())); @@ -417,6 +421,10 @@ public class ItemRegistryPopulator { itemNames.add(javaIdentifier); + if (firstMappingsPass && mappingItem.getDyeColor() != -1) { + dyeColors.put(itemIndex, mappingItem.getDyeColor()); + } + itemIndex++; } @@ -512,6 +520,10 @@ public class ItemRegistryPopulator { .build(); Registries.ITEMS.register(palette.getValue().protocolVersion(), itemMappings); + + firstMappingsPass = false; } + + ItemUtils.setDyeColors(dyeColors); } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/type/GeyserMappingItem.java b/core/src/main/java/org/geysermc/geyser/registry/type/GeyserMappingItem.java index a5b6c5ab8..9d06fd3a9 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/type/GeyserMappingItem.java +++ b/core/src/main/java/org/geysermc/geyser/registry/type/GeyserMappingItem.java @@ -44,4 +44,6 @@ public class GeyserMappingItem { @JsonProperty("tool_tier") String toolTier; @JsonProperty("max_damage") int maxDamage = 0; @JsonProperty("repair_materials") List repairMaterials; + @JsonProperty("has_suspicious_stew_effect") boolean hasSuspiciousStewEffect = false; + @JsonProperty("dye_color") int dyeColor = -1; } diff --git a/core/src/main/java/org/geysermc/geyser/registry/type/ItemMapping.java b/core/src/main/java/org/geysermc/geyser/registry/type/ItemMapping.java index ff558c55f..28d41ba46 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/type/ItemMapping.java +++ b/core/src/main/java/org/geysermc/geyser/registry/type/ItemMapping.java @@ -39,7 +39,7 @@ import java.util.Set; public class ItemMapping { public static final ItemMapping AIR = new ItemMapping("minecraft:air", "minecraft:air", 0, 0, 0, BlockRegistries.BLOCKS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()).getBedrockAirId(), - 64, null, null, null, 0, null); + 64, null, null, null, 0, null, false); String javaIdentifier; String bedrockIdentifier; @@ -63,6 +63,8 @@ public class ItemMapping { Set repairMaterials; + boolean hasSuspiciousStewEffect; + /** * Gets if this item is a block. * diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index c2e6ae6f6..e76f8405a 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -84,7 +84,6 @@ import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.connection.GeyserConnection; import org.geysermc.geyser.command.CommandSender; import org.geysermc.geyser.configuration.EmoteOffhandWorkaroundOption; -import org.geysermc.geyser.entity.InteractiveTagManager; import org.geysermc.geyser.entity.attribute.GeyserAttributeType; import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.ItemFrameEntity; @@ -449,6 +448,9 @@ public class GeyserSession implements GeyserConnection, CommandSender { */ private boolean flying = false; + @Setter + private boolean instabuild = false; + /** * Caches current rain status. */ @@ -1081,7 +1083,7 @@ public class GeyserSession implements GeyserConnection, CommandSender { if (mouseoverEntity != null) { // Horses, etc can change their property depending on if you're sneaking - InteractiveTagManager.updateTag(this, mouseoverEntity); + mouseoverEntity.updateInteractiveTag(); } } @@ -1531,4 +1533,17 @@ public class GeyserSession implements GeyserConnection, CommandSender { packet.getFogStack().addAll(this.fogNameSpaces); sendUpstreamPacket(packet); } + + public boolean canUseCommandBlocks() { + return instabuild && opPermissionLevel >= 2; + } + + public void playSoundEvent(SoundEvent sound, Vector3f position) { + LevelSoundEvent2Packet packet = new LevelSoundEvent2Packet(); + packet.setPosition(position); + packet.setSound(sound); + packet.setIdentifier(":"); + packet.setExtraData(-1); + sendUpstreamPacket(packet); + } } diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/TagCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/TagCache.java index 0f73737bb..d46a39616 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/TagCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/TagCache.java @@ -28,16 +28,19 @@ package org.geysermc.geyser.session.cache; import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundUpdateTagsPacket; import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.ints.IntLists; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.registry.type.BlockMapping; import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.session.GeyserSession; +import javax.annotation.ParametersAreNonnullByDefault; import java.util.Map; /** * Manages information sent from the {@link ClientboundUpdateTagsPacket}. If that packet is not sent, all lists here * will remain empty, matching Java Edition behavior. */ +@ParametersAreNonnullByDefault public class TagCache { /* Blocks */ private IntList leaves; @@ -54,9 +57,11 @@ public class TagCache { /* Items */ private IntList axolotlTemptItems; + private IntList fishes; private IntList flowers; private IntList foxFood; private IntList piglinLoved; + private IntList smallFlowers; public TagCache() { // Ensure all lists are non-null @@ -79,9 +84,11 @@ public class TagCache { Map itemTags = packet.getTags().get("minecraft:item"); this.axolotlTemptItems = IntList.of(itemTags.get("minecraft:axolotl_tempt_items")); + this.fishes = IntList.of(itemTags.get("minecraft:fishes")); this.flowers = IntList.of(itemTags.get("minecraft:flowers")); this.foxFood = IntList.of(itemTags.get("minecraft:fox_food")); this.piglinLoved = IntList.of(itemTags.get("minecraft:piglin_loved")); + this.smallFlowers = IntList.of(itemTags.get("minecraft:small_flowers")); // Hack btw boolean emulatePost1_14Logic = itemTags.get("minecraft:signs").length > 1; @@ -105,15 +112,21 @@ public class TagCache { this.requiresDiamondTool = IntLists.emptyList(); this.axolotlTemptItems = IntLists.emptyList(); + this.fishes = IntLists.emptyList(); this.flowers = IntLists.emptyList(); this.foxFood = IntLists.emptyList(); this.piglinLoved = IntLists.emptyList(); + this.smallFlowers = IntLists.emptyList(); } public boolean isAxolotlTemptItem(ItemMapping itemMapping) { return axolotlTemptItems.contains(itemMapping.getJavaId()); } + public boolean isFish(GeyserItemStack itemStack) { + return fishes.contains(itemStack.getJavaId()); + } + public boolean isFlower(ItemMapping mapping) { return flowers.contains(mapping.getJavaId()); } @@ -126,6 +139,10 @@ public class TagCache { return piglinLoved.contains(mapping.getJavaId()); } + public boolean isSmallFlower(GeyserItemStack itemStack) { + return smallFlowers.contains(itemStack.getJavaId()); + } + public boolean isAxeEffective(BlockMapping blockMapping) { return axeEffective.contains(blockMapping.getJavaBlockId()); } diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/WorldBorder.java b/core/src/main/java/org/geysermc/geyser/session/cache/WorldBorder.java index 00a080d8b..66922ff0b 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/WorldBorder.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/WorldBorder.java @@ -30,7 +30,6 @@ import com.nukkitx.math.vector.Vector2d; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.LevelEventType; import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; -import com.nukkitx.protocol.bedrock.packet.PlayerFogPacket; import lombok.Getter; import lombok.Setter; import org.geysermc.geyser.entity.EntityDefinitions; @@ -38,7 +37,6 @@ import org.geysermc.geyser.entity.type.player.PlayerEntity; import org.geysermc.geyser.session.GeyserSession; import javax.annotation.Nonnull; -import java.util.Collections; public class WorldBorder { private static final double DEFAULT_WORLD_BORDER_SIZE = 5.9999968E7D; @@ -131,11 +129,14 @@ public class WorldBorder { } /** - * @return true as long the entity is within the world limits. + * @return true as long as the player entity is within the world limits. */ public boolean isInsideBorderBoundaries() { - Vector3f entityPosition = session.getPlayerEntity().getPosition(); - return entityPosition.getX() > minX && entityPosition.getX() < maxX && entityPosition.getZ() > minZ && entityPosition.getZ() < maxZ; + return isInsideBorderBoundaries(session.getPlayerEntity().getPosition()); + } + + public boolean isInsideBorderBoundaries(Vector3f position) { + return position.getX() > minX && position.getX() < maxX && position.getZ() > minZ && position.getZ() < maxZ; } /** diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java index 869062da2..bd7b54def 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java @@ -33,10 +33,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; import com.github.steveice10.mc.protocol.data.game.entity.player.InteractAction; import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerAction; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundContainerClickPacket; -import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundInteractPacket; -import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundPlayerActionPacket; -import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundUseItemOnPacket; -import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundUseItemPacket; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.*; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.LevelEventType; @@ -45,7 +42,6 @@ import com.nukkitx.protocol.bedrock.packet.*; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import org.geysermc.geyser.entity.EntityDefinitions; -import org.geysermc.geyser.entity.type.CommandBlockMinecartEntity; import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.ItemFrameEntity; import org.geysermc.geyser.inventory.GeyserItemStack; @@ -58,8 +54,9 @@ import org.geysermc.geyser.registry.type.ItemMappings; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; -import org.geysermc.geyser.translator.sound.EntitySoundInteractionTranslator; import org.geysermc.geyser.util.BlockUtils; +import org.geysermc.geyser.util.EntityUtils; +import org.geysermc.geyser.util.InteractionResult; import org.geysermc.geyser.util.InventoryUtils; import java.util.List; @@ -151,14 +148,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator { @@ -84,7 +83,7 @@ public class BedrockInteractTranslator extends PacketTranslator return; } - InteractiveTagManager.updateTag(session, interactEntity); + interactEntity.updateInteractiveTag(); } else { if (session.getMouseoverEntity() != null) { // No interactive tag should be sent diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetEntityDataTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetEntityDataTranslator.java index ed9129c26..54c14f7f0 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetEntityDataTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetEntityDataTranslator.java @@ -28,7 +28,6 @@ package org.geysermc.geyser.translator.protocol.java.entity; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.ClientboundSetEntityDataPacket; import org.geysermc.geyser.entity.EntityDefinition; -import org.geysermc.geyser.entity.InteractiveTagManager; import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; @@ -60,8 +59,9 @@ public class JavaSetEntityDataTranslator extends PacketTranslator { - - /** - * Handles the block interaction when a player - * right-clicks an entity. - * - * @param session the session interacting with the block - * @param position the position of the block - * @param entity the entity interacted with - */ - static void handleEntityInteraction(GeyserSession session, Vector3f position, Entity entity) { - // If we need to get the hand identifier, only get it once and save it to a variable - String handIdentifier = null; - - for (Map.Entry> interactionEntry : Registries.SOUND_TRANSLATORS.get().entrySet()) { - if (!(interactionEntry.getValue() instanceof EntitySoundInteractionTranslator)) { - continue; - } - if (interactionEntry.getKey().entities().length != 0) { - boolean contains = false; - for (String entityIdentifier : interactionEntry.getKey().entities()) { - if (entity.getDefinition().entityType().name().toLowerCase().contains(entityIdentifier)) { - contains = true; - break; - } - } - if (!contains) continue; - } - GeyserItemStack itemInHand = session.getPlayerInventory().getItemInHand(); - if (interactionEntry.getKey().items().length != 0) { - if (itemInHand.isEmpty()) { - continue; - } - if (handIdentifier == null) { - // Don't get the identifier unless we need it - handIdentifier = itemInHand.getMapping(session).getJavaIdentifier(); - } - boolean contains = false; - for (String itemIdentifier : interactionEntry.getKey().items()) { - if (handIdentifier.contains(itemIdentifier)) { - contains = true; - break; - } - } - if (!contains) continue; - } - if (session.isSneaking() && !interactionEntry.getKey().ignoreSneakingWhileHolding()) { - if (!itemInHand.isEmpty()) { - continue; - } - } - ((EntitySoundInteractionTranslator) interactionEntry.getValue()).translate(session, position, entity); - } - } -} diff --git a/core/src/main/java/org/geysermc/geyser/translator/sound/SoundTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/sound/SoundTranslator.java index bb0e7c20a..0146c534e 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/sound/SoundTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/sound/SoundTranslator.java @@ -54,17 +54,6 @@ public @interface SoundTranslator { */ String[] items() default {}; - /** - * The identifier(s) that the interacted entity must have. - * Leave empty to ignore. - * - * Only applies to interaction handlers that are an - * instance of {@link EntitySoundInteractionTranslator}. - * - * @return the value the item in the player's hand must contain - */ - String[] entities() default {}; - /** * Controls if the interaction should still be * called even if the player is sneaking while diff --git a/core/src/main/java/org/geysermc/geyser/translator/sound/entity/FeedBabySoundInteractionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/sound/entity/FeedBabySoundInteractionTranslator.java deleted file mode 100644 index b996dafee..000000000 --- a/core/src/main/java/org/geysermc/geyser/translator/sound/entity/FeedBabySoundInteractionTranslator.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2019-2022 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.translator.sound.entity; - -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; -import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; -import org.geysermc.geyser.entity.type.Entity; -import org.geysermc.geyser.entity.type.living.animal.AnimalEntity; -import org.geysermc.geyser.entity.type.living.animal.OcelotEntity; -import org.geysermc.geyser.entity.type.living.animal.tameable.CatEntity; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.translator.sound.EntitySoundInteractionTranslator; -import org.geysermc.geyser.translator.sound.SoundTranslator; - -@SoundTranslator -public class FeedBabySoundInteractionTranslator implements EntitySoundInteractionTranslator { - - @Override - public void translate(GeyserSession session, Vector3f position, Entity entity) { - if (entity instanceof AnimalEntity animalEntity && !(entity instanceof CatEntity || entity instanceof OcelotEntity)) { - String handIdentifier = session.getPlayerInventory().getItemInHand().getMapping(session).getJavaIdentifier(); - boolean isBaby = animalEntity.isBaby(); - if (isBaby && animalEntity.canEat(handIdentifier.replace("minecraft:", ""), - session.getPlayerInventory().getItemInHand().getMapping(session))) { - // Play the "feed child" effect - EntityEventPacket feedEvent = new EntityEventPacket(); - feedEvent.setRuntimeEntityId(entity.getGeyserId()); - feedEvent.setType(EntityEventType.BABY_ANIMAL_FEED); - session.sendUpstreamPacket(feedEvent); - } - } - } -} diff --git a/core/src/main/java/org/geysermc/geyser/translator/sound/entity/MilkEntitySoundInteractionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/sound/entity/MilkEntitySoundInteractionTranslator.java deleted file mode 100644 index 49994f7e6..000000000 --- a/core/src/main/java/org/geysermc/geyser/translator/sound/entity/MilkEntitySoundInteractionTranslator.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2019-2022 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.translator.sound.entity; - -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.SoundEvent; -import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import com.nukkitx.protocol.bedrock.packet.LevelSoundEventPacket; -import org.geysermc.geyser.entity.type.Entity; -import org.geysermc.geyser.entity.type.living.animal.GoatEntity; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.translator.sound.EntitySoundInteractionTranslator; -import org.geysermc.geyser.translator.sound.SoundTranslator; - -@SoundTranslator(entities = {"cow", "goat"}, items = "bucket") -public class MilkEntitySoundInteractionTranslator implements EntitySoundInteractionTranslator { - - @Override - public void translate(GeyserSession session, Vector3f position, Entity value) { - if (!session.getPlayerInventory().getItemInHand().getMapping(session).getJavaIdentifier().equals("minecraft:bucket")) { - return; - } - if (value.getFlag(EntityFlag.BABY)) { - return; - } - - SoundEvent milkSound; - if (value instanceof GoatEntity && ((GoatEntity) value).isScreamer()) { - milkSound = SoundEvent.MILK_SCREAMER; - } else { - milkSound = SoundEvent.MILK; - } - LevelSoundEventPacket levelSoundEventPacket = new LevelSoundEventPacket(); - levelSoundEventPacket.setPosition(position); - levelSoundEventPacket.setBabySound(false); - levelSoundEventPacket.setRelativeVolumeDisabled(false); - levelSoundEventPacket.setIdentifier(":"); - levelSoundEventPacket.setSound(milkSound); - levelSoundEventPacket.setExtraData(-1); - session.sendUpstreamPacket(levelSoundEventPacket); - } -} diff --git a/core/src/main/java/org/geysermc/geyser/util/EntityUtils.java b/core/src/main/java/org/geysermc/geyser/util/EntityUtils.java index 1c89d38c4..5500abbc8 100644 --- a/core/src/main/java/org/geysermc/geyser/util/EntityUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/EntityUtils.java @@ -26,18 +26,25 @@ package org.geysermc.geyser.util; import com.github.steveice10.mc.protocol.data.game.entity.Effect; +import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.EntityDefinitions; +import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.living.ArmorStandEntity; import org.geysermc.geyser.entity.type.living.animal.AnimalEntity; +import org.geysermc.geyser.inventory.GeyserItemStack; +import org.geysermc.geyser.session.GeyserSession; import java.util.Locale; public final class EntityUtils { + /** + * A constant array of the two hands that a player can interact with an entity. + */ + public static final Hand[] HANDS = Hand.values(); /** * @return a new String array of all known effect identifiers @@ -197,6 +204,30 @@ public final class EntityUtils { } } + /** + * Determine if an action would result in a successful bucketing of the given entity. + */ + public static boolean attemptToBucket(GeyserSession session, Entity entityToBucket, GeyserItemStack itemInHand) { + if (itemInHand.getJavaId() == session.getItemMappings().getStoredItems().waterBucket() && entityToBucket.isAlive()) { + //TODO check bucket sound + return true; + } + return false; + } + + /** + * Attempt to determine the result of saddling the given entity. + */ + public static InteractionResult attemptToSaddle(GeyserSession session, Entity entityToSaddle, GeyserItemStack itemInHand) { + if (itemInHand.getJavaId() == session.getItemMappings().getStoredItems().saddle()) { + if (entityToSaddle.isAlive() && !entityToSaddle.getFlag(EntityFlag.SADDLED) && !entityToSaddle.getFlag(EntityFlag.BABY)) { + // Saddle + return InteractionResult.SUCCESS; + } + } + return InteractionResult.PASS; + } + private EntityUtils() { } } diff --git a/core/src/main/java/org/geysermc/geyser/util/InteractionResult.java b/core/src/main/java/org/geysermc/geyser/util/InteractionResult.java new file mode 100644 index 000000000..fd13dd743 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/util/InteractionResult.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2019-2022 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.util; + +/** + * Used as a mirror of Java Edition's own interaction enum. + */ +public enum InteractionResult { + CONSUME(true), + /** + * Indicates that the action does nothing, or in rare cases is not a priority. + */ + PASS(false), + /** + * Indicates that the action does something, and don't try to find another action to process. + */ + SUCCESS(true); + + private final boolean consumesAction; + + InteractionResult(boolean consumesAction) { + this.consumesAction = consumesAction; + } + + public boolean consumesAction() { + return consumesAction; + } + + public boolean shouldSwing() { + return this == SUCCESS; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/util/InteractiveTag.java b/core/src/main/java/org/geysermc/geyser/util/InteractiveTag.java new file mode 100644 index 000000000..1e8795478 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/util/InteractiveTag.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2019-2022 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.util; + +import lombok.Getter; + +import java.util.Locale; + +/** + * All interactive tags in enum form. For potential API usage. + */ +public enum InteractiveTag { + NONE((Void) null), + IGNITE_CREEPER("creeper"), + EDIT, + LEAVE_BOAT("exit.boat"), + FEED, + FISH("fishing"), + MILK, + MOOSHROOM_SHEAR("mooshear"), + MOOSHROOM_MILK_STEW("moostew"), + BOARD_BOAT("ride.boat"), + RIDE_MINECART("ride.minecart"), + RIDE_HORSE("ride.horse"), + RIDE_STRIDER("ride.strider"), + SHEAR, + SIT, + STAND, + TALK, + TAME, + DYE, + CURE, + OPEN_CONTAINER("opencontainer"), + CREATE_MAP("createMap"), + TAKE_PICTURE("takepicture"), + SADDLE, + MOUNT, + BOOST, + WRITE, + LEASH, + REMOVE_LEASH("unleash"), + NAME, + ATTACH_CHEST("attachchest"), + TRADE, + POSE_ARMOR_STAND("armorstand.pose"), + EQUIP_ARMOR_STAND("armorstand.equip"), + READ, + WAKE_VILLAGER("wakevillager"), + BARTER; + + /** + * The full string that should be passed on to the client. + */ + @Getter + private final String value; + + InteractiveTag(Void isNone) { + this.value = ""; + } + + InteractiveTag(String value) { + this.value = "action.interact." + value; + } + + InteractiveTag() { + this.value = "action.interact." + name().toLowerCase(Locale.ROOT); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/util/InventoryUtils.java b/core/src/main/java/org/geysermc/geyser/util/InventoryUtils.java index 29d8cd18c..5c2905d93 100644 --- a/core/src/main/java/org/geysermc/geyser/util/InventoryUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/InventoryUtils.java @@ -162,6 +162,13 @@ public class InventoryUtils { return item1.equals(item2, false, true, true); } + /** + * Checks to see if an item stack represents air or has no count. + */ + public static boolean isEmpty(@Nullable ItemStack itemStack) { + return itemStack == null || itemStack.getId() == ItemMapping.AIR.getJavaId() || itemStack.getAmount() <= 0; + } + /** * Returns a barrier block with custom name and lore to explain why * part of the inventory is unusable. diff --git a/core/src/main/java/org/geysermc/geyser/util/ItemUtils.java b/core/src/main/java/org/geysermc/geyser/util/ItemUtils.java index be1731079..d412247de 100644 --- a/core/src/main/java/org/geysermc/geyser/util/ItemUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/ItemUtils.java @@ -26,9 +26,11 @@ package org.geysermc.geyser.util; import com.github.steveice10.opennbt.tag.builtin.*; +import it.unimi.dsi.fastutil.ints.Int2IntMap; import org.geysermc.geyser.session.GeyserSession; public class ItemUtils { + private static Int2IntMap DYE_COLORS = null; public static int getEnchantmentLevel(CompoundTag itemNBTData, String enchantmentId) { ListTag enchantments = (itemNBTData == null ? null : itemNBTData.get("Enchantments")); @@ -73,4 +75,19 @@ public class ItemUtils { } return null; } + + /** + * Return the dye color associated with this Java item ID, if any. Returns -1 if no dye color exists for this item. + */ + public static int getDyeColor(int javaId) { + return DYE_COLORS.get(javaId); + } + + public static void setDyeColors(Int2IntMap dyeColors) { + if (DYE_COLORS != null) { + throw new RuntimeException(); + } + dyeColors.defaultReturnValue(-1); + DYE_COLORS = dyeColors; + } } From 9b8e1372c1dd5ca5918f6a1a76b6e78fda422bd1 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 25 Feb 2022 10:31:00 -0500 Subject: [PATCH 033/110] Work around dropping items from other slots Fixes #2846 --- ...BedrockInventoryTransactionTranslator.java | 35 ++++++++++++++++--- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java index 869062da2..260e06a50 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java @@ -43,6 +43,7 @@ import com.nukkitx.protocol.bedrock.data.LevelEventType; import com.nukkitx.protocol.bedrock.data.inventory.*; import com.nukkitx.protocol.bedrock.packet.*; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMaps; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.type.CommandBlockMinecartEntity; @@ -50,6 +51,7 @@ import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.ItemFrameEntity; import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.inventory.Inventory; +import org.geysermc.geyser.inventory.PlayerInventory; import org.geysermc.geyser.inventory.click.Click; import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.registry.BlockRegistries; @@ -91,18 +93,41 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator 1; + + if (session.getPlayerInventory().getHeldItemSlot() != containerAction.getSlot()) { + // Dropping an item that you don't have selected isn't supported in Java, but we can workaround it with an inventory hack + PlayerInventory inventory = session.getPlayerInventory(); + int hotbarSlot = inventory.getOffsetForHotbar(containerAction.getSlot()); + Click clickType = dropAll ? Click.DROP_ALL : Click.DROP_ONE; + Int2ObjectMap changedItem; + if (dropAll) { + inventory.setItem(hotbarSlot, GeyserItemStack.EMPTY, session); + changedItem = Int2ObjectMaps.singleton(hotbarSlot, null); + } else { + GeyserItemStack itemStack = inventory.getItem(hotbarSlot); + if (itemStack.isEmpty()) { + return; + } + itemStack.sub(1); + changedItem = Int2ObjectMaps.singleton(hotbarSlot, itemStack.getItemStack()); + } + ServerboundContainerClickPacket dropPacket = new ServerboundContainerClickPacket( + inventory.getId(), inventory.getStateId(), hotbarSlot, clickType.actionType, clickType.action, + inventory.getCursor().getItemStack(), changedItem); + session.sendDownstreamPacket(dropPacket); + return; + } + if (session.getPlayerInventory().getItemInHand().isEmpty()) { return; } - boolean dropAll = worldAction.getToItem().getCount() > 1; - ServerboundPlayerActionPacket dropAllPacket = new ServerboundPlayerActionPacket( + ServerboundPlayerActionPacket dropPacket = new ServerboundPlayerActionPacket( dropAll ? PlayerAction.DROP_ITEM_STACK : PlayerAction.DROP_ITEM, BlockUtils.POSITION_ZERO, Direction.DOWN ); - session.sendDownstreamPacket(dropAllPacket); + session.sendDownstreamPacket(dropPacket); if (dropAll) { session.getPlayerInventory().setItemInHand(GeyserItemStack.EMPTY); From dfbb5897b972e023d28229e804cba0d9129d4f6b Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 25 Feb 2022 13:48:34 -0500 Subject: [PATCH 034/110] Update mappings --- core/src/main/resources/mappings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/mappings b/core/src/main/resources/mappings index 8620c9c46..64f338a26 160000 --- a/core/src/main/resources/mappings +++ b/core/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit 8620c9c4603c16b74cbe1d6630695d243679896b +Subproject commit 64f338a2670bb8d300a66975389fc7887df4c4de From 0251bb64b8a1355b4f46fc3d27b7b6b5026b5878 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 26 Feb 2022 14:26:13 -0500 Subject: [PATCH 035/110] Update JLine Fixes #2867 --- bootstrap/standalone/pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bootstrap/standalone/pom.xml b/bootstrap/standalone/pom.xml index 0042a40d8..d0ef39bcf 100644 --- a/bootstrap/standalone/pom.xml +++ b/bootstrap/standalone/pom.xml @@ -47,17 +47,17 @@ org.jline jline-terminal - 3.20.0 + 3.21.0 org.jline jline-terminal-jna - 3.20.0 + 3.21.0 org.jline jline-reader - 3.20.0 + 3.21.0 org.apache.logging.log4j From d0220a9b71f9c210d5a99a0e5e08583cf19de3f9 Mon Sep 17 00:00:00 2001 From: turikhay Date: Sun, 27 Feb 2022 01:45:56 +0500 Subject: [PATCH 036/110] Allow single-device Microsoft authentication (#2688) By default, there is a two-minute delay if you disconnect so you can authenticate your Microsoft account. Co-authored-by: rtm516 Co-authored-by: Camotoy <20743703+Camotoy@users.noreply.github.com> --- .../java/org/geysermc/geyser/GeyserImpl.java | 5 + .../configuration/GeyserConfiguration.java | 2 + .../GeyserJacksonConfiguration.java | 3 + .../geyser/network/UpstreamPacketHandler.java | 7 + .../geyser/session/GeyserSession.java | 106 ++++++----- .../PendingMicrosoftAuthentication.java | 171 ++++++++++++++++++ .../geyser/util/LoginEncryptionUtils.java | 16 +- core/src/main/resources/config.yml | 4 + core/src/main/resources/languages | 2 +- 9 files changed, 259 insertions(+), 57 deletions(-) create mode 100644 core/src/main/java/org/geysermc/geyser/session/PendingMicrosoftAuthentication.java diff --git a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index 6226eec09..2577e6af1 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -59,6 +59,7 @@ import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.scoreboard.ScoreboardUpdater; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.session.PendingMicrosoftAuthentication; import org.geysermc.geyser.session.SessionManager; import org.geysermc.geyser.session.auth.AuthType; import org.geysermc.geyser.skin.FloodgateSkinUploader; @@ -125,6 +126,8 @@ public class GeyserImpl implements GeyserApi { private Metrics metrics; + private PendingMicrosoftAuthentication pendingMicrosoftAuthentication; + private static GeyserImpl instance; private GeyserImpl(PlatformType platformType, GeyserBootstrap bootstrap) { @@ -268,6 +271,8 @@ public class GeyserImpl implements GeyserApi { logger.debug("Not getting git properties for the news handler as we are in a development environment."); } + pendingMicrosoftAuthentication = new PendingMicrosoftAuthentication(config.getPendingAuthenticationTimeout()); + this.newsHandler = new NewsHandler(branch, buildNumber); CooldownUtils.setDefaultShowCooldown(config.getShowCooldown()); diff --git a/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java b/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java index 3b7cad44c..e2163675c 100644 --- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java @@ -100,6 +100,8 @@ public interface GeyserConfiguration { IMetricsInfo getMetrics(); + int getPendingAuthenticationTimeout(); + interface IBedrockConfiguration { String getAddress(); diff --git a/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java b/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java index 97c5bfea8..e8be96138 100644 --- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java @@ -141,6 +141,9 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration private MetricsInfo metrics = new MetricsInfo(); + @JsonProperty("pending-authentication-timeout") + private int pendingAuthenticationTimeout = 120; + @Getter @JsonIgnoreProperties(ignoreUnknown = true) public static class BedrockConfiguration implements IBedrockConfiguration { diff --git a/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java index f547c4dce..23542719a 100644 --- a/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java @@ -32,6 +32,7 @@ import com.nukkitx.protocol.bedrock.data.ResourcePackType; import com.nukkitx.protocol.bedrock.packet.*; import com.nukkitx.protocol.bedrock.v471.Bedrock_v471; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.session.PendingMicrosoftAuthentication; import org.geysermc.geyser.session.auth.AuthType; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.session.GeyserSession; @@ -199,6 +200,12 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { return true; } } + PendingMicrosoftAuthentication.AuthenticationTask task = geyser.getPendingMicrosoftAuthentication().getTask(session.getAuthData().xuid()); + if (task != null) { + if (task.getAuthentication().isDone() && session.onMicrosoftLoginComplete(task)) { + return true; + } + } return false; } diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index e76f8405a..d45276240 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -26,7 +26,6 @@ package org.geysermc.geyser.session; import com.github.steveice10.mc.auth.data.GameProfile; -import com.github.steveice10.mc.auth.exception.request.AuthPendingException; import com.github.steveice10.mc.auth.exception.request.InvalidCredentialsException; import com.github.steveice10.mc.auth.exception.request.RequestException; import com.github.steveice10.mc.auth.service.AuthenticationService; @@ -119,7 +118,6 @@ import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; import java.util.*; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -712,65 +710,57 @@ public class GeyserSession implements GeyserConnection, CommandSender { packet.setTime(16000); sendUpstreamPacket(packet); - // new thread so clients don't timeout - MsaAuthenticationService msaAuthenticationService = new MsaAuthenticationService(GeyserImpl.OAUTH_CLIENT_ID); + final PendingMicrosoftAuthentication.AuthenticationTask task = geyser.getPendingMicrosoftAuthentication().getOrCreateTask( + getAuthData().xuid() + ); + task.setOnline(true); + task.resetTimer(); - // Use a future to prevent timeouts as all the authentication is handled sync - // This will be changed with the new protocol library. - CompletableFuture.supplyAsync(() -> { - try { - return msaAuthenticationService.getAuthCode(); - } catch (RequestException e) { - throw new CompletionException(e); - } - }).whenComplete((response, ex) -> { - if (ex != null) { - ex.printStackTrace(); - disconnect(ex.toString()); - return; - } - LoginEncryptionUtils.buildAndShowMicrosoftCodeWindow(this, response); - attemptCodeAuthentication(msaAuthenticationService); - }); + if (task.getAuthentication().isDone()) { + onMicrosoftLoginComplete(task); + } else { + task.getCode().whenComplete((response, ex) -> { + boolean connected = !closed; + if (ex != null) { + if (connected) { + geyser.getLogger().error("Failed to get Microsoft auth code", ex); + disconnect(ex.toString()); + } + task.cleanup(); // error getting auth code -> clean up immediately + } else if (connected) { + LoginEncryptionUtils.buildAndShowMicrosoftCodeWindow(this, response); + task.getAuthentication().whenComplete((r, $) -> onMicrosoftLoginComplete(task)); + } + }); + } } - /** - * Poll every second to see if the user has successfully signed in - */ - private void attemptCodeAuthentication(MsaAuthenticationService msaAuthenticationService) { - if (loggedIn || closed) { - return; + public boolean onMicrosoftLoginComplete(PendingMicrosoftAuthentication.AuthenticationTask task) { + if (closed) { + return false; } - CompletableFuture.supplyAsync(() -> { - try { - msaAuthenticationService.login(); - GameProfile profile = msaAuthenticationService.getSelectedProfile(); - if (profile == null) { - // Java account is offline - disconnect(GeyserLocale.getPlayerLocaleString("geyser.network.remote.invalid_account", clientData.getLanguageCode())); - return null; - } - - return new MinecraftProtocol(profile, msaAuthenticationService.getAccessToken()); - } catch (RequestException e) { - throw new CompletionException(e); - } - }).whenComplete((response, ex) -> { - if (ex != null) { - if (!(ex instanceof CompletionException completionException) || !(completionException.getCause() instanceof AuthPendingException)) { - geyser.getLogger().error("Failed to log in with Microsoft code!", ex); - disconnect(ex.toString()); - } else { - // Wait one second before trying again - geyser.getScheduledThread().schedule(() -> attemptCodeAuthentication(msaAuthenticationService), 1, TimeUnit.SECONDS); - } - return; - } - if (!closed) { - this.protocol = response; + task.cleanup(); // player is online -> remove pending authentication immediately + Throwable ex = task.getLoginException(); + if (ex != null) { + geyser.getLogger().error("Failed to log in with Microsoft code!", ex); + disconnect(ex.toString()); + } else { + GameProfile selectedProfile = task.getMsaAuthenticationService().getSelectedProfile(); + if (selectedProfile == null) { + disconnect(GeyserLocale.getPlayerLocaleString( + "geyser.network.remote.invalid_account", + clientData.getLanguageCode() + )); + } else { + this.protocol = new MinecraftProtocol( + selectedProfile, + task.getMsaAuthenticationService().getAccessToken() + ); connectDownstream(); + return true; } - }); + } + return false; } /** @@ -970,6 +960,12 @@ public class GeyserSession implements GeyserConnection, CommandSender { geyser.getSessionManager().removeSession(this); upstream.disconnect(reason); } + if (authData != null) { + PendingMicrosoftAuthentication.AuthenticationTask task = geyser.getPendingMicrosoftAuthentication().getTask(authData.xuid()); + if (task != null) { + task.setOnline(false); + } + } } if (tickThread != null) { diff --git a/core/src/main/java/org/geysermc/geyser/session/PendingMicrosoftAuthentication.java b/core/src/main/java/org/geysermc/geyser/session/PendingMicrosoftAuthentication.java new file mode 100644 index 000000000..044389d24 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/session/PendingMicrosoftAuthentication.java @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2019-2021 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.session; + +import com.github.steveice10.mc.auth.exception.request.AuthPendingException; +import com.github.steveice10.mc.auth.exception.request.RequestException; +import com.github.steveice10.mc.auth.service.MsaAuthenticationService; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import lombok.Getter; +import lombok.NonNull; +import lombok.Setter; +import lombok.SneakyThrows; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.GeyserLogger; + +import javax.annotation.Nonnull; +import java.util.concurrent.*; + +/** + * Pending Microsoft authentication task cache. + * It permits user to exit the server while they authorize Geyser to access their Microsoft account. + */ +public class PendingMicrosoftAuthentication { + private final LoadingCache authentications; + + public PendingMicrosoftAuthentication(int timeoutSeconds) { + this.authentications = CacheBuilder.newBuilder() + .build(new CacheLoader<>() { + @Override + public AuthenticationTask load(@NonNull String userKey) { + return new AuthenticationTask(userKey, timeoutSeconds * 1000L); + } + }); + } + + public AuthenticationTask getTask(@Nonnull String userKey) { + return authentications.getIfPresent(userKey); + } + + @SneakyThrows(ExecutionException.class) + public AuthenticationTask getOrCreateTask(@Nonnull String userKey) { + return authentications.get(userKey); + } + + public class AuthenticationTask { + private static final Executor DELAYED_BY_ONE_SECOND = CompletableFuture.delayedExecutor(1, TimeUnit.SECONDS); + + @Getter + private final MsaAuthenticationService msaAuthenticationService = new MsaAuthenticationService(GeyserImpl.OAUTH_CLIENT_ID); + private final String userKey; + private final long timeoutMs; + + private long remainingTimeMs; + + @Setter + private boolean online = true; + + @Getter + private final CompletableFuture code; + @Getter + private final CompletableFuture authentication; + + @Getter + private volatile Throwable loginException; + + private AuthenticationTask(String userKey, long timeoutMs) { + this.userKey = userKey; + this.timeoutMs = timeoutMs; + this.remainingTimeMs = timeoutMs; + + // Request the code + this.code = CompletableFuture.supplyAsync(this::tryGetCode); + this.authentication = new CompletableFuture<>(); + // Once the code is received, continuously try to request the access token, profile, etc + this.code.thenRun(() -> performLoginAttempt(System.currentTimeMillis())); + this.authentication.whenComplete((r, ex) -> { + this.loginException = ex; + // avoid memory leak, in case player doesn't connect again + CompletableFuture.delayedExecutor(timeoutMs, TimeUnit.MILLISECONDS).execute(this::cleanup); + }); + } + + public void resetTimer() { + this.remainingTimeMs = this.timeoutMs; + } + + public void cleanup() { + GeyserLogger logger = GeyserImpl.getInstance().getLogger(); + if (logger.isDebug()) { + logger.debug("Cleaning up authentication task for " + userKey); + } + authentications.invalidate(userKey); + } + + private MsaAuthenticationService.MsCodeResponse tryGetCode() throws CompletionException { + try { + return msaAuthenticationService.getAuthCode(); + } catch (RequestException e) { + throw new CompletionException(e); + } + } + + private void performLoginAttempt(long lastAttempt) { + CompletableFuture.runAsync(() -> { + try { + msaAuthenticationService.login(); + } catch (AuthPendingException e) { + long currentAttempt = System.currentTimeMillis(); + if (!online) { + // decrement timer only when player's offline + remainingTimeMs -= currentAttempt - lastAttempt; + if (remainingTimeMs <= 0L) { + // time's up + authentication.completeExceptionally(new TaskTimeoutException()); + cleanup(); + return; + } + } + // try again in 1 second + performLoginAttempt(currentAttempt); + return; + } catch (Exception e) { + authentication.completeExceptionally(e); + return; + } + // login successful + authentication.complete(msaAuthenticationService); + }, DELAYED_BY_ONE_SECOND); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "{userKey='" + userKey + "'}"; + } + } + + /** + * @see PendingMicrosoftAuthentication + */ + public static class TaskTimeoutException extends Exception { + TaskTimeoutException() { + super("It took too long to authorize Geyser to access your Microsoft account. " + + "Please request new code and try again."); + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/util/LoginEncryptionUtils.java b/core/src/main/java/org/geysermc/geyser/util/LoginEncryptionUtils.java index 5a1063a10..dec138a3c 100644 --- a/core/src/main/java/org/geysermc/geyser/util/LoginEncryptionUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/LoginEncryptionUtils.java @@ -48,6 +48,7 @@ import org.geysermc.cumulus.SimpleForm; import org.geysermc.cumulus.response.CustomFormResponse; import org.geysermc.cumulus.response.ModalFormResponse; import org.geysermc.cumulus.response.SimpleFormResponse; +import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.GeyserLocale; import javax.crypto.SecretKey; @@ -312,10 +313,23 @@ public class LoginEncryptionUtils { * Shows the code that a user must input into their browser */ public static void buildAndShowMicrosoftCodeWindow(GeyserSession session, MsaAuthenticationService.MsCodeResponse msCode) { + StringBuilder message = new StringBuilder("%xbox.signin.website\n") + .append(ChatColor.AQUA) + .append("%xbox.signin.url") + .append(ChatColor.RESET) + .append("\n%xbox.signin.enterCode\n") + .append(ChatColor.GREEN) + .append(msCode.user_code); + int timeout = session.getGeyser().getConfig().getPendingAuthenticationTimeout(); + if (timeout != 0) { + message.append("\n\n") + .append(ChatColor.RESET) + .append(GeyserLocale.getPlayerLocaleString("geyser.auth.login.timeout", session.getLocale(), String.valueOf(timeout))); + } session.sendForm( ModalForm.builder() .title("%xbox.signin") - .content("%xbox.signin.website\n%xbox.signin.url\n%xbox.signin.enterCode\n" + msCode.user_code) + .content(message.toString()) .button1("%gui.done") .button2("%menu.disconnect") .responseHandler((form, responseData) -> { diff --git a/core/src/main/resources/config.yml b/core/src/main/resources/config.yml index 00e2521f3..9692adbf3 100644 --- a/core/src/main/resources/config.yml +++ b/core/src/main/resources/config.yml @@ -81,6 +81,10 @@ floodgate-key-file: key.pem # password: "this isn't really my password" # microsoft-account: false +# Specify how many seconds to wait while user authorizes Geyser to access their Microsoft account. +# User is allowed to disconnect from the server during this period. +pending-authentication-timeout: 120 + # Bedrock clients can freeze when opening up the command prompt for the first time if given a lot of commands. # Disabling this will prevent command suggestions from being sent and solve freezing for Bedrock clients. command-suggestions: true diff --git a/core/src/main/resources/languages b/core/src/main/resources/languages index bdee0d0f3..5db9d29ec 160000 --- a/core/src/main/resources/languages +++ b/core/src/main/resources/languages @@ -1 +1 @@ -Subproject commit bdee0d0f3f8a1271cd001f0bd0d672d0010be1db +Subproject commit 5db9d29ece0b3d810ae42f6bdc9eeefd76e3d99d From a435288bdfa15be6d7dccf7278317134888c95c5 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 26 Feb 2022 20:10:07 -0500 Subject: [PATCH 037/110] Properly map lit deepslate redstone ore --- core/src/main/resources/mappings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/mappings b/core/src/main/resources/mappings index 64f338a26..f73b45844 160000 --- a/core/src/main/resources/mappings +++ b/core/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit 64f338a2670bb8d300a66975389fc7887df4c4de +Subproject commit f73b45844f1185c3898db3052ce4ea0d18246168 From 65b68087b84aaccc78fc3664ace7ed04a993adc6 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 28 Feb 2022 10:07:45 -0500 Subject: [PATCH 038/110] Bump Geyser to 2.0.2 and Java to 1.18.2 --- README.md | 2 +- ap/pom.xml | 4 ++-- api/base/pom.xml | 2 +- api/geyser/pom.xml | 4 ++-- api/pom.xml | 2 +- bootstrap/bungeecord/pom.xml | 4 ++-- bootstrap/pom.xml | 4 ++-- bootstrap/spigot/pom.xml | 4 ++-- bootstrap/sponge/pom.xml | 4 ++-- bootstrap/standalone/pom.xml | 4 ++-- bootstrap/velocity/pom.xml | 4 ++-- common/pom.xml | 2 +- core/pom.xml | 10 +++++----- .../org/geysermc/geyser/network/MinecraftProtocol.java | 7 ++----- pom.xml | 2 +- 15 files changed, 28 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 593514e52..885ec920b 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have joined us here! -### Currently supporting Minecraft Bedrock 1.17.41 + 1.18.0 - 1.18.10 and Minecraft Java 1.18/1.18.1. +### Currently supporting Minecraft Bedrock 1.17.41 + 1.18.0 - 1.18.10 and Minecraft Java 1.18.2. ## Setting Up Take a look [here](https://github.com/GeyserMC/Geyser/wiki/Setup) for how to set up Geyser. diff --git a/ap/pom.xml b/ap/pom.xml index dce28a7d7..75f98275c 100644 --- a/ap/pom.xml +++ b/ap/pom.xml @@ -6,9 +6,9 @@ org.geysermc geyser-parent - 2.0.1-SNAPSHOT + 2.0.2-SNAPSHOT ap - 2.0.1-SNAPSHOT + 2.0.2-SNAPSHOT \ No newline at end of file diff --git a/api/base/pom.xml b/api/base/pom.xml index 17edb1a85..37e97ef7e 100644 --- a/api/base/pom.xml +++ b/api/base/pom.xml @@ -5,7 +5,7 @@ org.geysermc api-parent - 2.0.1-SNAPSHOT + 2.0.2-SNAPSHOT 4.0.0 diff --git a/api/geyser/pom.xml b/api/geyser/pom.xml index 26f4ddf30..9255bc579 100644 --- a/api/geyser/pom.xml +++ b/api/geyser/pom.xml @@ -5,7 +5,7 @@ org.geysermc api-parent - 2.0.1-SNAPSHOT + 2.0.2-SNAPSHOT 4.0.0 @@ -26,7 +26,7 @@ org.geysermc base-api - 2.0.1-SNAPSHOT + 2.0.2-SNAPSHOT compile diff --git a/api/pom.xml b/api/pom.xml index bc70be6c3..bae0da039 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -6,7 +6,7 @@ org.geysermc geyser-parent - 2.0.1-SNAPSHOT + 2.0.2-SNAPSHOT api-parent diff --git a/bootstrap/bungeecord/pom.xml b/bootstrap/bungeecord/pom.xml index 45a08c7db..f06a219bb 100644 --- a/bootstrap/bungeecord/pom.xml +++ b/bootstrap/bungeecord/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 2.0.1-SNAPSHOT + 2.0.2-SNAPSHOT bootstrap-bungeecord @@ -14,7 +14,7 @@ org.geysermc core - 2.0.1-SNAPSHOT + 2.0.2-SNAPSHOT compile diff --git a/bootstrap/pom.xml b/bootstrap/pom.xml index 58c651455..381f68bc2 100644 --- a/bootstrap/pom.xml +++ b/bootstrap/pom.xml @@ -6,7 +6,7 @@ org.geysermc geyser-parent - 2.0.1-SNAPSHOT + 2.0.2-SNAPSHOT bootstrap-parent pom @@ -34,7 +34,7 @@ org.geysermc ap - 2.0.1-SNAPSHOT + 2.0.2-SNAPSHOT provided diff --git a/bootstrap/spigot/pom.xml b/bootstrap/spigot/pom.xml index 6eda527f3..3a8939ce3 100644 --- a/bootstrap/spigot/pom.xml +++ b/bootstrap/spigot/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 2.0.1-SNAPSHOT + 2.0.2-SNAPSHOT bootstrap-spigot @@ -25,7 +25,7 @@ org.geysermc core - 2.0.1-SNAPSHOT + 2.0.2-SNAPSHOT compile diff --git a/bootstrap/sponge/pom.xml b/bootstrap/sponge/pom.xml index ab3b7d970..6285c6dbf 100644 --- a/bootstrap/sponge/pom.xml +++ b/bootstrap/sponge/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 2.0.1-SNAPSHOT + 2.0.2-SNAPSHOT bootstrap-sponge @@ -14,7 +14,7 @@ org.geysermc core - 2.0.1-SNAPSHOT + 2.0.2-SNAPSHOT compile diff --git a/bootstrap/standalone/pom.xml b/bootstrap/standalone/pom.xml index d0ef39bcf..6babc6933 100644 --- a/bootstrap/standalone/pom.xml +++ b/bootstrap/standalone/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 2.0.1-SNAPSHOT + 2.0.2-SNAPSHOT bootstrap-standalone @@ -18,7 +18,7 @@ org.geysermc core - 2.0.1-SNAPSHOT + 2.0.2-SNAPSHOT compile diff --git a/bootstrap/velocity/pom.xml b/bootstrap/velocity/pom.xml index ff052471d..1621d6ee6 100644 --- a/bootstrap/velocity/pom.xml +++ b/bootstrap/velocity/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 2.0.1-SNAPSHOT + 2.0.2-SNAPSHOT bootstrap-velocity @@ -14,7 +14,7 @@ org.geysermc core - 2.0.1-SNAPSHOT + 2.0.2-SNAPSHOT compile diff --git a/common/pom.xml b/common/pom.xml index fde2605bc..a563b7aff 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -6,7 +6,7 @@ org.geysermc geyser-parent - 2.0.1-SNAPSHOT + 2.0.2-SNAPSHOT common diff --git a/core/pom.xml b/core/pom.xml index f2812fea0..4a6d8b5ec 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -6,7 +6,7 @@ org.geysermc geyser-parent - 2.0.1-SNAPSHOT + 2.0.2-SNAPSHOT core @@ -21,19 +21,19 @@ org.geysermc ap - 2.0.1-SNAPSHOT + 2.0.2-SNAPSHOT provided org.geysermc geyser-api - 2.0.1-SNAPSHOT + 2.0.2-SNAPSHOT compile org.geysermc common - 2.0.1-SNAPSHOT + 2.0.2-SNAPSHOT compile @@ -155,7 +155,7 @@ com.github.GeyserMC MCProtocolLib - 6a23a780 + 7efa636 compile diff --git a/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java b/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java index c4bd05b13..c35b744e1 100644 --- a/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java +++ b/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java @@ -32,10 +32,7 @@ import com.nukkitx.protocol.bedrock.v471.Bedrock_v471; import com.nukkitx.protocol.bedrock.v475.Bedrock_v475; import com.nukkitx.protocol.bedrock.v486.Bedrock_v486; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.StringJoiner; +import java.util.*; /** * Contains information about the supported protocols in Geyser. @@ -92,7 +89,7 @@ public final class MinecraftProtocol { * @return the supported Minecraft: Java Edition version names */ public static List getJavaVersions() { - return Arrays.asList("1.18", "1.18.1"); + return Collections.singletonList(DEFAULT_JAVA_CODEC.getMinecraftVersion()); } /** diff --git a/pom.xml b/pom.xml index f00da7fc8..0599716fe 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 org.geysermc geyser-parent - 2.0.1-SNAPSHOT + 2.0.2-SNAPSHOT pom Geyser Allows for players from Minecraft Bedrock Edition to join Minecraft Java Edition servers. From 0fd903e0a02a2f4d8db8775534b0b8ba89182f38 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 28 Feb 2022 10:24:27 -0500 Subject: [PATCH 039/110] Pending Microsoft Authentication changes for GeyserConnect --- .../PendingMicrosoftAuthentication.java | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/geysermc/geyser/session/PendingMicrosoftAuthentication.java b/core/src/main/java/org/geysermc/geyser/session/PendingMicrosoftAuthentication.java index 044389d24..696d6b088 100644 --- a/core/src/main/java/org/geysermc/geyser/session/PendingMicrosoftAuthentication.java +++ b/core/src/main/java/org/geysermc/geyser/session/PendingMicrosoftAuthentication.java @@ -46,6 +46,10 @@ import java.util.concurrent.*; * It permits user to exit the server while they authorize Geyser to access their Microsoft account. */ public class PendingMicrosoftAuthentication { + /** + * For GeyserConnect usage. + */ + private boolean storeServerInformation = false; private final LoadingCache authentications; public PendingMicrosoftAuthentication(int timeoutSeconds) { @@ -53,7 +57,8 @@ public class PendingMicrosoftAuthentication { .build(new CacheLoader<>() { @Override public AuthenticationTask load(@NonNull String userKey) { - return new AuthenticationTask(userKey, timeoutSeconds * 1000L); + return storeServerInformation ? new ProxyAuthenticationTask(userKey, timeoutSeconds * 1000L) + : new AuthenticationTask(userKey, timeoutSeconds * 1000L); } }); } @@ -67,6 +72,11 @@ public class PendingMicrosoftAuthentication { return authentications.get(userKey); } + @SuppressWarnings("unused") // GeyserConnect + public void setStoreServerInformation() { + storeServerInformation = true; + } + public class AuthenticationTask { private static final Executor DELAYED_BY_ONE_SECOND = CompletableFuture.delayedExecutor(1, TimeUnit.SECONDS); @@ -159,6 +169,17 @@ public class PendingMicrosoftAuthentication { } } + @Getter + @Setter + public final class ProxyAuthenticationTask extends AuthenticationTask { + private String server; + private int port; + + private ProxyAuthenticationTask(String userKey, long timeoutMs) { + super(userKey, timeoutMs); + } + } + /** * @see PendingMicrosoftAuthentication */ From e163301d2315de337b98f335c550d2abe3bf217f Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 1 Mar 2022 15:44:13 -0500 Subject: [PATCH 040/110] Fix StackOverflow possibility with undead horses --- .../animal/horse/AbstractHorseEntity.java | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/AbstractHorseEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/AbstractHorseEntity.java index de26e380e..f573840a6 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/AbstractHorseEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/AbstractHorseEntity.java @@ -130,6 +130,11 @@ public class AbstractHorseEntity extends AnimalEntity { @Nonnull @Override protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) { + return testHorseInteraction(itemInHand); + } + + @Nonnull + protected InteractiveTag testHorseInteraction(@Nonnull GeyserItemStack itemInHand) { boolean isBaby = isBaby(); if (!isBaby) { if (getFlag(EntityFlag.TAMED) && session.isSneaking()) { @@ -175,6 +180,11 @@ public class AbstractHorseEntity extends AnimalEntity { @Nonnull @Override protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) { + return mobHorseInteract(itemInHand); + } + + @Nonnull + protected final InteractionResult mobHorseInteract(@Nonnull GeyserItemStack itemInHand) { boolean isBaby = isBaby(); if (!isBaby) { if (getFlag(EntityFlag.TAMED) && session.isSneaking()) { @@ -243,11 +253,11 @@ public class AbstractHorseEntity extends AnimalEntity { if (!getFlag(EntityFlag.TAMED)) { return InteractiveTag.NONE; } else if (isBaby()) { - return testMobInteraction(itemInHand); + return testHorseInteraction(itemInHand); } else if (session.isSneaking()) { return InteractiveTag.OPEN_CONTAINER; } else if (!passengers.isEmpty()) { - return testMobInteraction(itemInHand); + return testHorseInteraction(itemInHand); } else { if (session.getItemMappings().getStoredItems().saddle() == itemInHand.getJavaId()) { return InteractiveTag.OPEN_CONTAINER; @@ -265,12 +275,12 @@ public class AbstractHorseEntity extends AnimalEntity { if (!getFlag(EntityFlag.TAMED)) { return InteractionResult.PASS; } else if (isBaby()) { - return mobInteract(itemInHand); + return mobHorseInteract(itemInHand); } else if (session.isSneaking()) { // Opens inventory return InteractionResult.SUCCESS; } else if (!passengers.isEmpty()) { - return mobInteract(itemInHand); + return mobHorseInteract(itemInHand); } else { // The client tests for saddle but it doesn't matter for us at this point. return InteractionResult.SUCCESS; From 17fb38e567aca5bca9b1a7b1d2012321c180f433 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 1 Mar 2022 22:50:11 -0500 Subject: [PATCH 041/110] This method can be final --- .../entity/type/living/animal/horse/AbstractHorseEntity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/AbstractHorseEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/AbstractHorseEntity.java index f573840a6..9139495b8 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/AbstractHorseEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/AbstractHorseEntity.java @@ -134,7 +134,7 @@ public class AbstractHorseEntity extends AnimalEntity { } @Nonnull - protected InteractiveTag testHorseInteraction(@Nonnull GeyserItemStack itemInHand) { + protected final InteractiveTag testHorseInteraction(@Nonnull GeyserItemStack itemInHand) { boolean isBaby = isBaby(); if (!isBaby) { if (getFlag(EntityFlag.TAMED) && session.isSneaking()) { From 37c854b5ac68c7322d5d7ee9fb04185afb612d56 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 1 Mar 2022 22:52:30 -0500 Subject: [PATCH 042/110] Fix our PacketLib selection not being used --- core/pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/pom.xml b/core/pom.xml index 4a6d8b5ec..5b509e417 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -147,7 +147,7 @@ - com.github.RednedEpic + com.github.GeyserMC MCAuthLib 6c99331 compile @@ -159,11 +159,11 @@ compile - com.github.steveice10 + com.github.GeyserMC packetlib - com.github.steveice10 + com.github.GeyserMC mcauthlib From c977e36368a91484a13386bcd29094029e60c69d Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 3 Mar 2022 18:52:26 -0500 Subject: [PATCH 043/110] Deprecate userAuths in favor of a saved token system --- .gitignore | 5 +- .../standalone/GeyserStandaloneBootstrap.java | 6 ++ core/pom.xml | 2 +- .../java/org/geysermc/geyser/Constants.java | 2 + .../org/geysermc/geyser/GeyserBootstrap.java | 7 ++ .../java/org/geysermc/geyser/GeyserImpl.java | 84 ++++++++++++++++++- .../configuration/GeyserConfiguration.java | 3 + .../GeyserJacksonConfiguration.java | 3 + .../geyser/network/UpstreamPacketHandler.java | 8 ++ .../geyser/session/GeyserSession.java | 64 ++++++++++++-- .../PendingMicrosoftAuthentication.java | 21 +++-- ...SetLocalPlayerAsInitializedTranslator.java | 12 ++- .../geyser/util/LoginEncryptionUtils.java | 42 ++++++++++ core/src/main/resources/config.yml | 23 ++--- core/src/main/resources/languages | 2 +- 15 files changed, 251 insertions(+), 33 deletions(-) diff --git a/.gitignore b/.gitignore index 85f8a6e9e..401002e1d 100644 --- a/.gitignore +++ b/.gitignore @@ -239,8 +239,9 @@ nbdist/ run/ config.yml logs/ -public-key.pem +key.pem locales/ /cache/ /packs/ -/dump.json \ No newline at end of file +/dump.json +/saved-refresh-tokens.json \ No newline at end of file diff --git a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java index 43ab4b3fe..7c49585d5 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java @@ -275,6 +275,12 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { return Paths.get(System.getProperty("user.dir")); } + @Override + public Path getSavedUserLoginsFolder() { + // Return the location of the config + return new File(configFilename).getAbsoluteFile().getParentFile().toPath(); + } + @Override public BootstrapDumpInfo getDumpInfo() { return new GeyserStandaloneDumpInfo(this); diff --git a/core/pom.xml b/core/pom.xml index 5b509e417..51fc149e3 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -149,7 +149,7 @@ com.github.GeyserMC MCAuthLib - 6c99331 + d9d773e compile diff --git a/core/src/main/java/org/geysermc/geyser/Constants.java b/core/src/main/java/org/geysermc/geyser/Constants.java index 49f8fa676..23fb76d16 100644 --- a/core/src/main/java/org/geysermc/geyser/Constants.java +++ b/core/src/main/java/org/geysermc/geyser/Constants.java @@ -37,6 +37,8 @@ public final class Constants { public static final String FLOODGATE_DOWNLOAD_LOCATION = "https://ci.opencollab.dev/job/GeyserMC/job/Floodgate/job/master/"; + static final String SAVED_REFRESH_TOKEN_FILE = "saved-refresh-tokens.json"; + static { URI wsUri = null; try { diff --git a/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java b/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java index 54ca36787..d40060310 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java @@ -97,6 +97,13 @@ public interface GeyserBootstrap { */ Path getConfigFolder(); + /** + * @return the folder where user tokens are saved. This should always point to the location of the config. + */ + default Path getSavedUserLoginsFolder() { + return getConfigFolder(); + } + /** * Information used for the bootstrap section of the debug dump * diff --git a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index 2577e6af1..f3ebfa4a3 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -26,6 +26,7 @@ package org.geysermc.geyser; import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.github.steveice10.packetlib.tcp.TcpSession; @@ -37,6 +38,7 @@ import io.netty.channel.kqueue.KQueue; import io.netty.util.NettyRuntime; import io.netty.util.concurrent.DefaultThreadFactory; import io.netty.util.internal.SystemPropertyUtil; +import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; import org.checkerframework.checker.nullness.qual.NonNull; @@ -72,6 +74,9 @@ import org.geysermc.geyser.util.*; import javax.naming.directory.Attribute; import javax.naming.directory.InitialDirContext; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; import java.io.InputStream; import java.net.InetAddress; import java.net.InetSocketAddress; @@ -79,6 +84,7 @@ import java.net.UnknownHostException; import java.security.Key; import java.text.DecimalFormat; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.regex.Matcher; @@ -127,6 +133,8 @@ public class GeyserImpl implements GeyserApi { private Metrics metrics; private PendingMicrosoftAuthentication pendingMicrosoftAuthentication; + @Getter(AccessLevel.NONE) + private Map savedRefreshTokens; private static GeyserImpl instance; @@ -325,7 +333,7 @@ public class GeyserImpl implements GeyserApi { metrics = new Metrics(this, "GeyserMC", config.getMetrics().getUniqueId(), false, java.util.logging.Logger.getLogger("")); metrics.addCustomChart(new Metrics.SingleLineChart("players", sessionManager::size)); // Prevent unwanted words best we can - metrics.addCustomChart(new Metrics.SimplePie("authMode", () -> config.getRemote().getAuthType().toString().toLowerCase())); + metrics.addCustomChart(new Metrics.SimplePie("authMode", () -> config.getRemote().getAuthType().toString().toLowerCase(Locale.ROOT))); metrics.addCustomChart(new Metrics.SimplePie("platform", platformType::getPlatformName)); metrics.addCustomChart(new Metrics.SimplePie("defaultLocale", GeyserLocale::getDefaultLocale)); metrics.addCustomChart(new Metrics.SimplePie("version", () -> GeyserImpl.VERSION)); @@ -409,6 +417,47 @@ public class GeyserImpl implements GeyserApi { metrics = null; } + if (config.getRemote().getAuthType() == AuthType.ONLINE) { + if (config.getUserAuths() != null && !config.getUserAuths().isEmpty()) { + getLogger().warning("The 'userAuths' config section is now deprecated, and will be removed in the near future! " + + "Please migrate to the new 'saved-user-logins' config option: " + + "https://wiki.geysermc.org/geyser/understanding-the-config/"); + } + + // May be written/read to on multiple threads from each GeyserSession as well as writing the config + savedRefreshTokens = new ConcurrentHashMap<>(); + + File tokensFile = bootstrap.getSavedUserLoginsFolder().resolve(Constants.SAVED_REFRESH_TOKEN_FILE).toFile(); + if (tokensFile.exists()) { + TypeReference> type = new TypeReference<>() { }; + + Map refreshTokenFile = null; + try { + refreshTokenFile = JSON_MAPPER.readValue(tokensFile, type); + } catch (IOException e) { + logger.error("Cannot load saved user tokens!", e); + } + if (refreshTokenFile != null) { + List validUsers = config.getSavedUserLogins(); + boolean doWrite = false; + for (Map.Entry entry : refreshTokenFile.entrySet()) { + String user = entry.getKey(); + if (!validUsers.contains(user)) { + // Perform a write to this file to purge the now-unused name + doWrite = true; + continue; + } + savedRefreshTokens.put(user, entry.getValue()); + } + if (doWrite) { + scheduleRefreshTokensWrite(); + } + } + } + } else { + savedRefreshTokens = null; + } + newsHandler.handleNews(null, NewsItemAction.ON_SERVER_STARTED); } @@ -516,6 +565,39 @@ public class GeyserImpl implements GeyserApi { return bootstrap.getWorldManager(); } + @Nullable + public String refreshTokenFor(@NonNull String bedrockName) { + return savedRefreshTokens.get(bedrockName); + } + + public void saveRefreshToken(@NonNull String bedrockName, @NonNull String refreshToken) { + if (!getConfig().getSavedUserLogins().contains(bedrockName)) { + // Do not save this login + return; + } + + // We can safely overwrite old instances because MsaAuthenticationService#getLoginResponseFromRefreshToken + // refreshes the token for us + if (!Objects.equals(refreshToken, savedRefreshTokens.put(bedrockName, refreshToken))) { + scheduleRefreshTokensWrite(); + } + } + + private void scheduleRefreshTokensWrite() { + scheduledThread.execute(() -> { + // Ensure all writes are handled on the same thread + File savedTokens = getBootstrap().getSavedUserLoginsFolder().resolve(Constants.SAVED_REFRESH_TOKEN_FILE).toFile(); + TypeReference> type = new TypeReference<>() { }; + try (FileWriter writer = new FileWriter(savedTokens)) { + JSON_MAPPER.writerFor(type) + .withDefaultPrettyPrinter() + .writeValue(writer, savedRefreshTokens); + } catch (IOException e) { + getLogger().error("Unable to write saved refresh tokens!", e); + } + }); + } + public static GeyserImpl getInstance() { return instance; } diff --git a/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java b/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java index e2163675c..7bb73a648 100644 --- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java @@ -44,6 +44,9 @@ public interface GeyserConfiguration { IRemoteConfiguration getRemote(); + List getSavedUserLogins(); + + @Deprecated Map getUserAuths(); boolean isCommandSuggestions(); diff --git a/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java b/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java index e8be96138..463350441 100644 --- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java @@ -62,6 +62,9 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration private BedrockConfiguration bedrock = new BedrockConfiguration(); private RemoteConfiguration remote = new RemoteConfiguration(); + @JsonProperty("saved-user-logins") + private List savedUserLogins = Collections.emptyList(); + @JsonProperty("floodgate-key-file") private String floodgateKeyFile = "key.pem"; diff --git a/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java index 23542719a..24ede03c1 100644 --- a/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java @@ -190,6 +190,14 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { } private boolean couldLoginUserByName(String bedrockUsername) { + if (geyser.getConfig().getSavedUserLogins().contains(bedrockUsername)) { + String refreshToken = geyser.refreshTokenFor(bedrockUsername); + if (refreshToken != null) { + geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.auth.stored_credentials", session.getAuthData().name())); + session.authenticateWithRefreshToken(refreshToken); + return true; + } + } if (geyser.getConfig().getUserAuths() != null) { GeyserConfiguration.IUserAuthenticationInfo info = geyser.getConfig().getUserAuths().get(bedrockUsername); diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index d45276240..437044a6d 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -637,7 +637,6 @@ public class GeyserSession implements GeyserConnection, CommandSender { loggingIn = true; // Use a future to prevent timeouts as all the authentication is handled sync - // This will be changed with the new protocol library. CompletableFuture.supplyAsync(() -> { try { if (password != null && !password.isEmpty()) { @@ -694,10 +693,58 @@ public class GeyserSession implements GeyserConnection, CommandSender { }); } + public void authenticateWithRefreshToken(String refreshToken) { + if (loggedIn) { + geyser.getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.auth.already_loggedin", getAuthData().name())); + return; + } + + loggingIn = true; + + CompletableFuture.supplyAsync(() -> { + MsaAuthenticationService service = new MsaAuthenticationService(GeyserImpl.OAUTH_CLIENT_ID); + service.setRefreshToken(refreshToken); + try { + service.login(); + } catch (RequestException e) { + geyser.getLogger().error("Error while attempting to use refresh token for " + name() + "!", e); + return Boolean.FALSE; + } + + GameProfile profile = service.getSelectedProfile(); + if (profile == null) { + // Java account is offline + disconnect(GeyserLocale.getPlayerLocaleString("geyser.network.remote.invalid_account", clientData.getLanguageCode())); + return null; + } + + protocol = new MinecraftProtocol(profile, service.getAccessToken()); + geyser.saveRefreshToken(name(), service.getRefreshToken()); + return Boolean.TRUE; + }).whenComplete((successful, ex) -> { + if (this.closed) { + return; + } + if (successful == Boolean.FALSE) { + // The player is waiting for a spawn packet, so let's spawn them in now to show them forms + connect(); + // Will be cached for after login + LoginEncryptionUtils.buildAndShowTokenExpiredWindow(this); + return; + } + + connectDownstream(); + }); + } + + public void authenticateWithMicrosoftCode() { + authenticateWithMicrosoftCode(false); + } + /** * Present a form window to the user asking to log in with another web browser */ - public void authenticateWithMicrosoftCode() { + public void authenticateWithMicrosoftCode(boolean offlineAccess) { if (loggedIn) { geyser.getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.auth.already_loggedin", getAuthData().name())); return; @@ -719,7 +766,7 @@ public class GeyserSession implements GeyserConnection, CommandSender { if (task.getAuthentication().isDone()) { onMicrosoftLoginComplete(task); } else { - task.getCode().whenComplete((response, ex) -> { + task.getCode(offlineAccess).whenComplete((response, ex) -> { boolean connected = !closed; if (ex != null) { if (connected) { @@ -735,6 +782,9 @@ public class GeyserSession implements GeyserConnection, CommandSender { } } + /** + * If successful, also begins connecting to the Java server. + */ public boolean onMicrosoftLoginComplete(PendingMicrosoftAuthentication.AuthenticationTask task) { if (closed) { return false; @@ -745,7 +795,8 @@ public class GeyserSession implements GeyserConnection, CommandSender { geyser.getLogger().error("Failed to log in with Microsoft code!", ex); disconnect(ex.toString()); } else { - GameProfile selectedProfile = task.getMsaAuthenticationService().getSelectedProfile(); + MsaAuthenticationService service = task.getMsaAuthenticationService(); + GameProfile selectedProfile = service.getSelectedProfile(); if (selectedProfile == null) { disconnect(GeyserLocale.getPlayerLocaleString( "geyser.network.remote.invalid_account", @@ -754,9 +805,12 @@ public class GeyserSession implements GeyserConnection, CommandSender { } else { this.protocol = new MinecraftProtocol( selectedProfile, - task.getMsaAuthenticationService().getAccessToken() + service.getAccessToken() ); connectDownstream(); + + // Save our refresh token for later use + geyser.saveRefreshToken(name(), service.getRefreshToken()); return true; } } diff --git a/core/src/main/java/org/geysermc/geyser/session/PendingMicrosoftAuthentication.java b/core/src/main/java/org/geysermc/geyser/session/PendingMicrosoftAuthentication.java index 696d6b088..93200dcb6 100644 --- a/core/src/main/java/org/geysermc/geyser/session/PendingMicrosoftAuthentication.java +++ b/core/src/main/java/org/geysermc/geyser/session/PendingMicrosoftAuthentication.java @@ -90,8 +90,6 @@ public class PendingMicrosoftAuthentication { @Setter private boolean online = true; - @Getter - private final CompletableFuture code; @Getter private final CompletableFuture authentication; @@ -103,11 +101,7 @@ public class PendingMicrosoftAuthentication { this.timeoutMs = timeoutMs; this.remainingTimeMs = timeoutMs; - // Request the code - this.code = CompletableFuture.supplyAsync(this::tryGetCode); this.authentication = new CompletableFuture<>(); - // Once the code is received, continuously try to request the access token, profile, etc - this.code.thenRun(() -> performLoginAttempt(System.currentTimeMillis())); this.authentication.whenComplete((r, ex) -> { this.loginException = ex; // avoid memory leak, in case player doesn't connect again @@ -127,9 +121,20 @@ public class PendingMicrosoftAuthentication { authentications.invalidate(userKey); } - private MsaAuthenticationService.MsCodeResponse tryGetCode() throws CompletionException { + public CompletableFuture getCode(boolean offlineAccess) { + // Request the code + CompletableFuture code = CompletableFuture.supplyAsync(() -> tryGetCode(offlineAccess)); + // Once the code is received, continuously try to request the access token, profile, etc + code.thenRun(() -> performLoginAttempt(System.currentTimeMillis())); + return code; + } + + /** + * @param offlineAccess whether we want a refresh token for later use. + */ + private MsaAuthenticationService.MsCodeResponse tryGetCode(boolean offlineAccess) throws CompletionException { try { - return msaAuthenticationService.getAuthCode(); + return msaAuthenticationService.getAuthCode(offlineAccess); } catch (RequestException e) { throw new CompletionException(e); } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockSetLocalPlayerAsInitializedTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockSetLocalPlayerAsInitializedTranslator.java index e55b28602..8641a35ff 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockSetLocalPlayerAsInitializedTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockSetLocalPlayerAsInitializedTranslator.java @@ -43,7 +43,17 @@ public class BedrockSetLocalPlayerAsInitializedTranslator extends PacketTranslat if (session.getRemoteAuthType() == AuthType.ONLINE) { if (!session.isLoggedIn()) { - LoginEncryptionUtils.buildAndShowLoginWindow(session); + if (session.getGeyser().getConfig().getSavedUserLogins().contains(session.name())) { + if (session.getGeyser().refreshTokenFor(session.name()) == null) { + LoginEncryptionUtils.buildAndShowConsentWindow(session); + } else { + // If the refresh token is not null and we're here, then the refresh token expired + // and the expiration form has been cached + session.getFormCache().resendAllForms(); + } + } else { + LoginEncryptionUtils.buildAndShowLoginWindow(session); + } } // else we were able to log the user in } diff --git a/core/src/main/java/org/geysermc/geyser/util/LoginEncryptionUtils.java b/core/src/main/java/org/geysermc/geyser/util/LoginEncryptionUtils.java index dec138a3c..2ed754963 100644 --- a/core/src/main/java/org/geysermc/geyser/util/LoginEncryptionUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/LoginEncryptionUtils.java @@ -262,6 +262,48 @@ public class LoginEncryptionUtils { })); } + /** + * Build a window that explains the user's credentials will be saved to the system. + */ + public static void buildAndShowConsentWindow(GeyserSession session) { + String locale = session.getLocale(); + session.sendForm( + SimpleForm.builder() + .title("%gui.signIn") + .content(GeyserLocale.getPlayerLocaleString("geyser.auth.login.save_token.warning", locale) + + "\n\n" + + GeyserLocale.getPlayerLocaleString("geyser.auth.login.save_token.proceed", locale)) + .button("%gui.ok") + .button("%gui.decline") + .responseHandler((form, responseData) -> { + SimpleFormResponse response = form.parseResponse(responseData); + if (response.isCorrect() && response.getClickedButtonId() == 0) { + session.authenticateWithMicrosoftCode(true); + } else { + session.disconnect("%disconnect.quitting"); + } + })); + } + + public static void buildAndShowTokenExpiredWindow(GeyserSession session) { + String locale = session.getLocale(); + session.sendForm( + SimpleForm.builder() + .title(GeyserLocale.getPlayerLocaleString("geyser.auth.login.form.expired", locale)) + .content(GeyserLocale.getPlayerLocaleString("geyser.auth.login.save_token.expired", locale) + + "\n\n" + + GeyserLocale.getPlayerLocaleString("geyser.auth.login.save_token.proceed", locale)) + .button("%gui.ok") + .responseHandler((form, responseData) -> { + SimpleFormResponse response = form.parseResponse(responseData); + if (response.isCorrect()) { + session.authenticateWithMicrosoftCode(true); + } else { + session.disconnect("%disconnect.quitting"); + } + })); + } + public static void buildAndShowLoginDetailsWindow(GeyserSession session) { session.sendForm( CustomForm.builder() diff --git a/core/src/main/resources/config.yml b/core/src/main/resources/config.yml index 9692adbf3..2582e4d4d 100644 --- a/core/src/main/resources/config.yml +++ b/core/src/main/resources/config.yml @@ -66,20 +66,15 @@ remote: # If you're using a plugin version of Floodgate on the same server, the key will automatically be picked up from Floodgate. floodgate-key-file: key.pem -# The Xbox/Minecraft Bedrock username is the key for the Java server auth-info. -# This allows automatic configuration/login to the remote Java server. -# If you are brave enough to put your Mojang account info into a config file. -# Uncomment the lines below to enable this feature. -#userAuths: -# BedrockAccountUsername: # Your Minecraft: Bedrock Edition username -# email: javaccountemail@example.com # Your Minecraft: Java Edition email -# password: javaccountpassword123 # Your Minecraft: Java Edition password -# microsoft-account: true # Whether the account is a Mojang or Microsoft account. -# -# bluerkelp2: -# email: not_really_my_email_address_mr_minecrafter53267@gmail.com -# password: "this isn't really my password" -# microsoft-account: false +# For online mode authentication type only. +# Stores a list of Bedrock players that should have their Java Edition account saved after login. +# This saves a token that can be reused to authenticate the player later. This does not save emails or passwords, +# but you should still be cautious when adding to this list and giving others access to this Geyser instance's files. +# Removing a name from this list will delete its cached login information on the next Geyser startup. +# The file for this is in the same folder as this config, named "saved-refresh-tokens.json". +saved-user-logins: + - ThisExampleUsernameShouldBeLongEnoughToNeverBeAnXboxUsername + - ThisOtherExampleUsernameShouldAlsoBeLongEnough # Specify how many seconds to wait while user authorizes Geyser to access their Microsoft account. # User is allowed to disconnect from the server during this period. diff --git a/core/src/main/resources/languages b/core/src/main/resources/languages index 5db9d29ec..c03eea033 160000 --- a/core/src/main/resources/languages +++ b/core/src/main/resources/languages @@ -1 +1 @@ -Subproject commit 5db9d29ece0b3d810ae42f6bdc9eeefd76e3d99d +Subproject commit c03eea033cb61ece135cd795ce04b34dd39a02f8 From be93d0cc8141dfa92e2b6324efca8536cf9445c9 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 4 Mar 2022 15:25:18 -0500 Subject: [PATCH 044/110] Remove PR checks for Sonarcloud --- .github/workflows/sonarcloud.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index 64ac19621..598cab46a 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -3,8 +3,6 @@ on: push: branches: - master - pull_request: - types: [opened, synchronize, reopened] jobs: build: name: SonarCloud From a58239f15b85f6f2ed87734f662059557297ca3b Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 4 Mar 2022 15:25:33 -0500 Subject: [PATCH 045/110] Update Adapters to support 1.18.2 --- bootstrap/spigot/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrap/spigot/pom.xml b/bootstrap/spigot/pom.xml index 3a8939ce3..da8b184e9 100644 --- a/bootstrap/spigot/pom.xml +++ b/bootstrap/spigot/pom.xml @@ -43,7 +43,7 @@ org.geysermc.geyser.adapters spigot-all - 1.3-SNAPSHOT + 1.4-SNAPSHOT From edbb946d974fb33d7772dc6830e0ebbd231acf81 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 4 Mar 2022 23:24:38 -0500 Subject: [PATCH 046/110] Indicate Geyser supports 1.18.10 AND .12 --- .../java/org/geysermc/geyser/network/MinecraftProtocol.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java b/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java index c35b744e1..7ab381375 100644 --- a/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java +++ b/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java @@ -57,7 +57,9 @@ public final class MinecraftProtocol { static { SUPPORTED_BEDROCK_CODECS.add(Bedrock_v471.V471_CODEC); SUPPORTED_BEDROCK_CODECS.add(Bedrock_v475.V475_CODEC.toBuilder().minecraftVersion("1.18.0/1.18.1/1.18.2").build()); - SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC); + SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC.toBuilder() + .minecraftVersion("1.18.10/1.18.12") // 1.18.11 is also supported, but was only on Switch and since that auto-updates it's not needed + .build()); } /** From 50bed6a2bef888b7a12621774f4e5f1fafad1886 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 5 Mar 2022 22:15:25 -0500 Subject: [PATCH 047/110] Use a dummy legacy event hover serializer This reduces computation processing needing, since Bedrock doesn't have any hover text ability. This also fixes a 1.8 bug where villager titles would not process correctly - by having a dummy serializer, a recent MCProtocolLib update would not stop the window packet from processing. --- core/pom.xml | 2 +- .../text/DummyLegacyHoverEventSerializer.java | 69 +++++++++++++++++++ .../inventory/JavaOpenScreenTranslator.java | 6 +- .../translator/text/MessageTranslator.java | 14 ++-- 4 files changed, 80 insertions(+), 11 deletions(-) create mode 100644 core/src/main/java/org/geysermc/geyser/text/DummyLegacyHoverEventSerializer.java diff --git a/core/pom.xml b/core/pom.xml index 51fc149e3..c5d80db8a 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -155,7 +155,7 @@ com.github.GeyserMC MCProtocolLib - 7efa636 + 0771504 compile diff --git a/core/src/main/java/org/geysermc/geyser/text/DummyLegacyHoverEventSerializer.java b/core/src/main/java/org/geysermc/geyser/text/DummyLegacyHoverEventSerializer.java new file mode 100644 index 000000000..fdce1f879 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/text/DummyLegacyHoverEventSerializer.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2019-2022 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.text; + +import net.kyori.adventure.key.Key; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.HoverEvent; +import net.kyori.adventure.text.serializer.gson.LegacyHoverEventSerializer; +import net.kyori.adventure.util.Codec; +import org.jetbrains.annotations.NotNull; + +import java.nio.charset.StandardCharsets; +import java.util.UUID; + +public final class DummyLegacyHoverEventSerializer implements LegacyHoverEventSerializer { + private final HoverEvent.ShowEntity dummyShowEntity; + private final HoverEvent.ShowItem dummyShowItem; + + public DummyLegacyHoverEventSerializer() { + dummyShowEntity = HoverEvent.ShowEntity.of(Key.key("geysermc", "dummyshowitem"), + UUID.nameUUIDFromBytes("entitiesareprettyneat".getBytes(StandardCharsets.UTF_8))); + dummyShowItem = HoverEvent.ShowItem.of(Key.key("geysermc", "dummyshowentity"), 0); + } + + @Override + public HoverEvent.@NotNull ShowItem deserializeShowItem(@NotNull Component input) { + return dummyShowItem; + } + + @Override + public HoverEvent.@NotNull ShowEntity deserializeShowEntity(@NotNull Component input, + Codec.Decoder componentDecoder) { + return dummyShowEntity; + } + + @Override + public @NotNull Component serializeShowItem(HoverEvent.@NotNull ShowItem input) { + return Component.empty(); + } + + @Override + public @NotNull Component serializeShowEntity(HoverEvent.@NotNull ShowEntity input, + Codec.Encoder componentEncoder) { + return Component.empty(); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaOpenScreenTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaOpenScreenTranslator.java index e68cae2e7..eedde6098 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaOpenScreenTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaOpenScreenTranslator.java @@ -29,12 +29,11 @@ import com.github.steveice10.mc.protocol.packet.ingame.clientbound.inventory.Cli import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundContainerClosePacket; import org.geysermc.geyser.inventory.Inventory; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.inventory.InventoryTranslator; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; import org.geysermc.geyser.translator.text.MessageTranslator; -import org.geysermc.geyser.translator.inventory.InventoryTranslator; import org.geysermc.geyser.util.InventoryUtils; -import org.geysermc.geyser.text.MinecraftLocale; @Translator(packet = ClientboundOpenScreenPacket.class) public class JavaOpenScreenTranslator extends PacketTranslator { @@ -57,8 +56,7 @@ public class JavaOpenScreenTranslator extends PacketTranslator Date: Sat, 5 Mar 2022 22:32:38 -0500 Subject: [PATCH 048/110] Simplify disconnection logging A disconnect message will always be printed, but not more than once. CLOSED_BY_REMOTE_PEER -> Bedrock client disconnected (hopefully slightly less vague) If a message is sent from the server, the log will now indicate their disconnection reason. --- .../geyser/network/UpstreamPacketHandler.java | 2 -- .../geyser/session/GeyserSession.java | 27 ++++++++++++------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java index 24ede03c1..5ded35259 100644 --- a/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java @@ -74,11 +74,9 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { String supportedVersions = MinecraftProtocol.getAllSupportedBedrockVersions(); if (loginPacket.getProtocolVersion() > MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) { // Too early to determine session locale - session.getGeyser().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.outdated.server", supportedVersions)); session.disconnect(GeyserLocale.getLocaleStringLog("geyser.network.outdated.server", supportedVersions)); return true; } else if (loginPacket.getProtocolVersion() < MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) { - session.getGeyser().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.outdated.client", supportedVersions)); session.disconnect(GeyserLocale.getLocaleStringLog("geyser.network.outdated.client", supportedVersions)); return true; } diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index 437044a6d..6452803b5 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -112,6 +112,7 @@ import org.geysermc.geyser.util.DimensionUtils; import org.geysermc.geyser.util.LoginEncryptionUtils; import org.geysermc.geyser.util.MathUtils; +import javax.annotation.Nonnull; import java.net.ConnectException; import java.net.InetAddress; import java.net.InetSocketAddress; @@ -125,13 +126,13 @@ import java.util.concurrent.atomic.AtomicInteger; @Getter public class GeyserSession implements GeyserConnection, CommandSender { - private final GeyserImpl geyser; - private final UpstreamSession upstream; + private final @Nonnull GeyserImpl geyser; + private final @Nonnull UpstreamSession upstream; /** * The loop where all packets and ticking is processed to prevent concurrency issues. * If this is manually called, ensure that any exceptions are properly handled. */ - private final EventLoop eventLoop; + private final @Nonnull EventLoop eventLoop; private TcpSession downstream; @Setter private AuthData authData; @@ -547,11 +548,14 @@ public class GeyserSession implements GeyserConnection, CommandSender { } bedrockServerSession.addDisconnectHandler(disconnectReason -> { - InetAddress address = bedrockServerSession.getRealAddress().getAddress(); - geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.disconnect", address, disconnectReason)); + String message = switch (disconnectReason) { + // A generic message that just means the player quit normally. + case CLOSED_BY_REMOTE_PEER -> GeyserLocale.getLocaleStringLog("geyser.network.disconnect.closed_by_remote_peer"); + case TIMED_OUT -> GeyserLocale.getLocaleStringLog("geyser.network.disconnect.timed_out"); + default -> disconnectReason.name(); + }; - disconnect(disconnectReason.name()); - geyser.getSessionManager().removeSession(this); + disconnect(message); }); this.remoteAddress = geyser.getConfig().getRemote().getAddress(); @@ -1009,11 +1013,16 @@ public class GeyserSession implements GeyserConnection, CommandSender { loggedIn = false; if (downstream != null) { downstream.disconnect(reason); + } else { + // Downstream's disconnect will fire an event that prints a log message + // Otherwise, we print a message here + InetAddress address = upstream.getAddress().getAddress(); + geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.disconnect", address, reason)); } - if (upstream != null && !upstream.isClosed()) { - geyser.getSessionManager().removeSession(this); + if (!upstream.isClosed()) { upstream.disconnect(reason); } + geyser.getSessionManager().removeSession(this); if (authData != null) { PendingMicrosoftAuthentication.AuthenticationTask task = geyser.getPendingMicrosoftAuthentication().getTask(authData.xuid()); if (task != null) { From a1bb4343a38ffdc59445090f91403c9bc287c93e Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 5 Mar 2022 22:33:43 -0500 Subject: [PATCH 049/110] Update languages submodule --- core/src/main/resources/languages | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/languages b/core/src/main/resources/languages index c03eea033..d2a01218d 160000 --- a/core/src/main/resources/languages +++ b/core/src/main/resources/languages @@ -1 +1 @@ -Subproject commit c03eea033cb61ece135cd795ce04b34dd39a02f8 +Subproject commit d2a01218d43f5b60bd4512d5eb6ad7e03a097f8c From 9c0b9f19759b09193ff0c6b7bbe18c51a3988385 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 7 Mar 2022 13:58:09 -0500 Subject: [PATCH 050/110] Show attribute suggestions in commands --- .../protocol/java/JavaCommandsTranslator.java | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCommandsTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCommandsTranslator.java index 28ebca926..00b60fec0 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCommandsTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCommandsTranslator.java @@ -27,6 +27,8 @@ package org.geysermc.geyser.translator.protocol.java; import com.github.steveice10.mc.protocol.data.game.command.CommandNode; import com.github.steveice10.mc.protocol.data.game.command.CommandParser; +import com.github.steveice10.mc.protocol.data.game.command.properties.ResourceProperties; +import com.github.steveice10.mc.protocol.data.game.entity.attribute.AttributeType; import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundCommandsPacket; import com.nukkitx.protocol.bedrock.data.command.CommandData; import com.nukkitx.protocol.bedrock.data.command.CommandEnumData; @@ -58,6 +60,7 @@ import java.util.*; public class JavaCommandsTranslator extends PacketTranslator { private static final String[] ALL_EFFECT_IDENTIFIERS = EntityUtils.getAllEffectIdentifiers(); + private static final String[] ATTRIBUTES = AttributeType.Builtin.BUILTIN.keySet().toArray(new String[0]); private static final String[] ENUM_BOOLEAN = {"true", "false"}; private static final String[] VALID_COLORS; private static final String[] VALID_SCOREBOARD_SLOTS; @@ -203,10 +206,11 @@ public class JavaCommandsTranslator extends PacketTranslator VALID_COLORS; case SCOREBOARD_SLOT -> VALID_SCOREBOARD_SLOTS; case MOB_EFFECT -> ALL_EFFECT_IDENTIFIERS; + case RESOURCE, RESOURCE_OR_TAG -> { + String resource = ((ResourceProperties) node.getProperties()).getRegistryKey(); + if (resource.equals("minecraft:attribute")) { + yield ATTRIBUTES; + } else { + yield CommandParam.STRING; + } + } default -> CommandParam.STRING; }; } @@ -302,7 +314,7 @@ public class JavaCommandsTranslator extends PacketTranslator Date: Wed, 9 Mar 2022 23:09:48 -0500 Subject: [PATCH 051/110] Remove unlockedRecipes storage This has been unused, and for the time being we aren't going the packet route that would use these. --- .../geyser/session/GeyserSession.java | 2 - .../protocol/java/JavaRecipeTranslator.java | 53 ------------------- .../java/JavaUpdateRecipesTranslator.java | 1 - 3 files changed, 56 deletions(-) delete mode 100644 core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaRecipeTranslator.java diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index 6452803b5..fe63b0b0c 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -349,7 +349,6 @@ public class GeyserSession implements GeyserConnection, CommandSender { @Setter private Int2ObjectMap craftingRecipes; - private final Set unlockedRecipes; private final AtomicInteger lastRecipeNetId; /** @@ -527,7 +526,6 @@ public class GeyserSession implements GeyserConnection, CommandSender { this.playerInventory = new PlayerInventory(); this.openInventory = null; this.craftingRecipes = new Int2ObjectOpenHashMap<>(); - this.unlockedRecipes = new ObjectOpenHashSet<>(); this.lastRecipeNetId = new AtomicInteger(1); this.spawned = false; diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaRecipeTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaRecipeTranslator.java deleted file mode 100644 index da35da60e..000000000 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaRecipeTranslator.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2019-2022 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.translator.protocol.java; - -import com.github.steveice10.mc.protocol.data.game.UnlockRecipesAction; -import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundRecipePacket; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.translator.protocol.PacketTranslator; -import org.geysermc.geyser.translator.protocol.Translator; - -import java.util.Collections; - -/** - * Used to list recipes that we can definitely use the recipe book for (and therefore save on packet usage) - */ -@Translator(packet = ClientboundRecipePacket.class) -public class JavaRecipeTranslator extends PacketTranslator { - - @Override - public void translate(GeyserSession session, ClientboundRecipePacket packet) { - if (packet.getAction() == UnlockRecipesAction.REMOVE) { - for (String identifier : packet.getRecipes()) { - session.getUnlockedRecipes().remove(identifier); - } - } else { - Collections.addAll(session.getUnlockedRecipes(), packet.getRecipes()); - } - } -} - diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaUpdateRecipesTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaUpdateRecipesTranslator.java index 4d7a1617a..722ce988e 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaUpdateRecipesTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaUpdateRecipesTranslator.java @@ -201,7 +201,6 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator Date: Thu, 10 Mar 2022 15:16:08 -0500 Subject: [PATCH 052/110] Fix inability to toggle sitting of parrots --- .../type/living/animal/tameable/TameableEntity.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/TameableEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/TameableEntity.java index 50d17eaaa..33b2144e8 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/TameableEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/TameableEntity.java @@ -61,7 +61,13 @@ public class TameableEntity extends AnimalEntity { // Note: Must be set for wolf collar color to work if (entityMetadata.getValue().isPresent()) { // Owner UUID of entity - Entity entity = session.getEntityCache().getPlayerEntity(entityMetadata.getValue().get()); + UUID uuid = entityMetadata.getValue().get(); + Entity entity; + if (uuid.equals(session.getPlayerEntity().getUuid())) { + entity = session.getPlayerEntity(); + } else { + entity = session.getEntityCache().getPlayerEntity(uuid); + } // Used as both a check since the player isn't in the entity cache and a normal fallback if (entity == null) { // Set to tame, but indicate that we are not the player that owns this From 0829b5cd4ebadd11ebef203c5c624eedad477bb3 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 15 Mar 2022 13:34:56 -0400 Subject: [PATCH 053/110] Replicate Bedrock shield behavior more accurately If the player swings, then they cannot be holding their shield at the same time. Also fixes an animation edge case with other players. --- .../geyser/entity/type/LivingEntity.java | 22 +++- .../type/player/SessionPlayerEntity.java | 11 ++ .../geyser/session/GeyserSession.java | 112 +++++++++++++++++- .../bedrock/BedrockAnimateTranslator.java | 6 +- ...BedrockInventoryTransactionTranslator.java | 20 ++-- .../player/BedrockActionTranslator.java | 36 +----- .../java/entity/JavaAnimateTranslator.java | 3 + 7 files changed, 156 insertions(+), 54 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java index a5214854e..0cce0f8df 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java @@ -99,13 +99,15 @@ public class LivingEntity extends Entity { public void setLivingEntityFlags(ByteEntityMetadata entityMetadata) { byte xd = entityMetadata.getPrimitiveValue(); - // Blocking gets triggered when using a bow, but if we set USING_ITEM for all items, it may look like - // you're "mining" with ex. a shield. + boolean isUsingItem = (xd & 0x01) == 0x01; + boolean isUsingOffhand = (xd & 0x02) == 0x02; + ItemMapping shield = session.getItemMappings().getStoredItems().shield(); - boolean isUsingShield = (getHand().getId() == shield.getBedrockId() || - getHand().equals(ItemData.AIR) && getOffHand().getId() == shield.getBedrockId()); - setFlag(EntityFlag.USING_ITEM, (xd & 0x01) == 0x01 && !isUsingShield); - setFlag(EntityFlag.BLOCKING, (xd & 0x01) == 0x01); + boolean isUsingShield = hasShield(isUsingOffhand, shield); + + setFlag(EntityFlag.USING_ITEM, isUsingItem && !isUsingShield); + // Override the blocking + setFlag(EntityFlag.BLOCKING, isUsingItem && isUsingShield); // Riptide spin attack setFlag(EntityFlag.DAMAGE_NEARBY_MOBS, (xd & 0x04) == 0x04); @@ -142,6 +144,14 @@ public class LivingEntity extends Entity { } } + protected boolean hasShield(boolean offhand, ItemMapping shieldMapping) { + if (offhand) { + return offHand.getId() == shieldMapping.getBedrockId(); + } else { + return hand.getId() == shieldMapping.getBedrockId(); + } + } + @Override protected boolean isShaking() { return isMaxFrozenState; diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java index 8dd24bdb8..077f82171 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java @@ -38,6 +38,7 @@ import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import lombok.Getter; import org.geysermc.geyser.entity.attribute.GeyserAttributeType; +import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.util.AttributeUtils; @@ -167,6 +168,16 @@ public class SessionPlayerEntity extends PlayerEntity { return super.createHealthAttribute(); } + @Override + protected boolean hasShield(boolean offhand, ItemMapping shieldMapping) { + // Must be overridden to point to the player's inventory cache + if (offhand) { + return session.getPlayerInventory().getOffhand().getJavaId() == shieldMapping.getJavaId(); + } else { + return session.getPlayerInventory().getItemInHand().getJavaId() == shieldMapping.getJavaId(); + } + } + @Override public void updateBedrockMetadata() { super.updateBedrockMetadata(); diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index fe63b0b0c..3cfe0e550 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -36,8 +36,11 @@ import com.github.steveice10.mc.protocol.MinecraftProtocol; import com.github.steveice10.mc.protocol.data.ProtocolState; import com.github.steveice10.mc.protocol.data.UnexpectedEncryptionException; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose; +import com.github.steveice10.mc.protocol.data.game.entity.object.Direction; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; +import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; import com.github.steveice10.mc.protocol.data.game.entity.player.HandPreference; +import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerAction; import com.github.steveice10.mc.protocol.data.game.setting.ChatVisibility; import com.github.steveice10.mc.protocol.data.game.setting.SkinPart; import com.github.steveice10.mc.protocol.data.game.statistic.CustomStatistic; @@ -46,6 +49,8 @@ import com.github.steveice10.mc.protocol.packet.handshake.serverbound.ClientInte import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundClientInformationPacket; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundMovePlayerPosPacket; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundPlayerAbilitiesPacket; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundPlayerActionPacket; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundUseItemPacket; import com.github.steveice10.mc.protocol.packet.login.serverbound.ServerboundCustomQueryPacket; import com.github.steveice10.packetlib.BuiltinFlags; import com.github.steveice10.packetlib.Session; @@ -97,6 +102,7 @@ import org.geysermc.geyser.level.physics.CollisionManager; import org.geysermc.geyser.network.netty.LocalSession; import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.type.BlockMappings; +import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMappings; import org.geysermc.geyser.session.auth.AuthData; import org.geysermc.geyser.session.auth.AuthType; @@ -107,10 +113,7 @@ import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.text.MinecraftLocale; import org.geysermc.geyser.translator.inventory.InventoryTranslator; import org.geysermc.geyser.translator.text.MessageTranslator; -import org.geysermc.geyser.util.ChunkUtils; -import org.geysermc.geyser.util.DimensionUtils; -import org.geysermc.geyser.util.LoginEncryptionUtils; -import org.geysermc.geyser.util.MathUtils; +import org.geysermc.geyser.util.*; import javax.annotation.Nonnull; import java.net.ConnectException; @@ -422,6 +425,13 @@ public class GeyserSession implements GeyserConnection, CommandSender { @Setter private long lastVehicleMoveTimestamp = System.currentTimeMillis(); + /** + * Counts how many ticks have occurred since an arm animation started. + * -1 means there is no active arm swing. + */ + @Getter(AccessLevel.NONE) + private int armAnimationTicks = -1; + /** * Controls whether the daylight cycle gamerule has been sent to the client, so the sun/moon remain motionless. */ @@ -1107,6 +1117,34 @@ public class GeyserSession implements GeyserConnection, CommandSender { for (Tickable entity : entityCache.getTickableEntities()) { entity.tick(); } + + if (armAnimationTicks != -1) { + // As of 1.18.2 Java Edition, it appears that the swing time is dynamically updated depending on the + // player's effect status, but the animation can cut short if the duration suddenly decreases + // (from suddenly no longer having mining fatigue, for example) + // This math is referenced from Java Edition 1.18.2 + int swingTotalDuration; + int hasteLevel = Math.max(effectCache.getHaste(), effectCache.getConduitPower()); + if (hasteLevel > 0) { + swingTotalDuration = 6 - hasteLevel; + } else { + int miningFatigueLevel = effectCache.getMiningFatigue(); + if (miningFatigueLevel > 0) { + swingTotalDuration = 6 + miningFatigueLevel * 2; + } else { + swingTotalDuration = 6; + } + } + if (++armAnimationTicks >= swingTotalDuration) { + if (sneaking) { + // Attempt to re-activate blocking as our swing animation is up + if (attemptToBlock()) { + playerEntity.updateBedrockMetadata(); + } + } + armAnimationTicks = -1; + } + } } catch (Throwable throwable) { throwable.printStackTrace(); } @@ -1116,7 +1154,23 @@ public class GeyserSession implements GeyserConnection, CommandSender { this.authData = authData; } - public void setSneaking(boolean sneaking) { + public void startSneaking() { + // Toggle the shield, if there is no ongoing arm animation + // This matches Bedrock Edition behavior as of 1.18.12 + if (armAnimationTicks == -1) { + attemptToBlock(); + } + + setSneaking(true); + } + + public void stopSneaking() { + disableBlocking(); + + setSneaking(false); + } + + private void setSneaking(boolean sneaking) { this.sneaking = sneaking; // Update pose and bounding box on our end @@ -1201,6 +1255,54 @@ public class GeyserSession implements GeyserConnection, CommandSender { return null; } + /** + * Checks to see if a shield is in either hand to activate blocking. If so, it sets the Bedrock client to display + * blocking and sends a packet to the Java server. + */ + private boolean attemptToBlock() { + ItemMapping shield = itemMappings.getStoredItems().shield(); + + ServerboundUseItemPacket useItemPacket; + if (playerInventory.getItemInHand().getJavaId() == shield.getJavaId()) { + useItemPacket = new ServerboundUseItemPacket(Hand.MAIN_HAND); + } else if (playerInventory.getOffhand().getJavaId() == shield.getJavaId()) { + useItemPacket = new ServerboundUseItemPacket(Hand.OFF_HAND); + } else { + // No blocking + return false; + } + + sendDownstreamPacket(useItemPacket); + playerEntity.setFlag(EntityFlag.BLOCKING, true); + // Metadata should be updated later + return true; + } + + /** + * Starts ticking the amount of time that the Bedrock client has been swinging their arm, and disables blocking if + * blocking. + */ + public void activateArmAnimationTicking() { + armAnimationTicks = 0; + if (disableBlocking()) { + playerEntity.updateBedrockMetadata(); + } + } + + /** + * Indicates to the client to stop blocking and tells the Java server the same. + */ + private boolean disableBlocking() { + if (playerEntity.getFlag(EntityFlag.BLOCKING)) { + ServerboundPlayerActionPacket releaseItemPacket = new ServerboundPlayerActionPacket(PlayerAction.RELEASE_USE_ITEM, + BlockUtils.POSITION_ZERO, Direction.DOWN); + sendDownstreamPacket(releaseItemPacket); + playerEntity.setFlag(EntityFlag.BLOCKING, false); + return true; + } + return false; + } + /** * Will be overwritten for GeyserConnect. */ diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockAnimateTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockAnimateTranslator.java index ac4a4bb2e..670e55785 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockAnimateTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockAnimateTranslator.java @@ -48,8 +48,10 @@ public class BedrockAnimateTranslator extends PacketTranslator { switch (packet.getAction()) { case SWING_ARM -> // Delay so entity damage can be processed first - session.scheduleInEventLoop(() -> - session.sendDownstreamPacket(new ServerboundSwingPacket(Hand.MAIN_HAND)), + session.scheduleInEventLoop(() -> { + session.sendDownstreamPacket(new ServerboundSwingPacket(Hand.MAIN_HAND)); + session.activateArmAnimationTicking(); + }, 25, TimeUnit.MILLISECONDS ); diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java index 7129c1318..f120e4a19 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java @@ -411,22 +411,20 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator processEntityInteraction(session, packet, entity); // Interact + case 1 -> { // Attack + int entityId; if (entity.getDefinition() == EntityDefinitions.ENDER_DRAGON) { // Redirects the attack to its body entity, this only happens when // attacking the underbelly of the ender dragon - ServerboundInteractPacket attackPacket = new ServerboundInteractPacket(entity.getEntityId() + 3, - InteractAction.ATTACK, session.isSneaking()); - session.sendDownstreamPacket(attackPacket); + entityId = entity.getEntityId() + 3; } else { - ServerboundInteractPacket attackPacket = new ServerboundInteractPacket(entity.getEntityId(), - InteractAction.ATTACK, session.isSneaking()); - session.sendDownstreamPacket(attackPacket); + entityId = entity.getEntityId(); } - break; + ServerboundInteractPacket attackPacket = new ServerboundInteractPacket(entityId, + InteractAction.ATTACK, session.isSneaking()); + session.sendDownstreamPacket(attackPacket); + } } break; } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockActionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockActionTranslator.java index 52129797b..5429899fa 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockActionTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockActionTranslator.java @@ -28,7 +28,10 @@ package org.geysermc.geyser.translator.protocol.bedrock.entity.player; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.github.steveice10.mc.protocol.data.game.entity.object.Direction; import com.github.steveice10.mc.protocol.data.game.entity.player.*; -import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.*; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundInteractPacket; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundPlayerAbilitiesPacket; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundPlayerActionPacket; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundPlayerCommandPacket; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.LevelEventType; @@ -39,10 +42,8 @@ import com.nukkitx.protocol.bedrock.packet.*; import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.ItemFrameEntity; import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; -import org.geysermc.geyser.inventory.PlayerInventory; import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.registry.BlockRegistries; -import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; @@ -105,38 +106,13 @@ public class BedrockActionTranslator extends PacketTranslator Date: Fri, 18 Mar 2022 10:51:22 -0400 Subject: [PATCH 054/110] More descriptive disconnect messages for outdated Java servers --- .../network/session/GeyserSession.java | 4 +- .../geyser/session/GeyserSession.java | 15 -------- .../java/JavaLoginDisconnectTranslator.java | 37 ++++++++++++++++++- 3 files changed, 38 insertions(+), 18 deletions(-) diff --git a/core/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/core/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 85bfd583d..890290a01 100644 --- a/core/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -100,7 +100,7 @@ public class GeyserSession { } public void login() { - this.handle.login(); + throw new UnsupportedOperationException(); } public void authenticate(String username) { @@ -120,7 +120,7 @@ public class GeyserSession { } public void close() { - this.handle.close(); + throw new UnsupportedOperationException(); } public void executeInEventLoop(Runnable runnable) { diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index 3cfe0e550..8a030c385 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -625,17 +625,6 @@ public class GeyserSession implements GeyserConnection, CommandSender { upstream.sendPacket(gamerulePacket); } - public void login() { - if (this.remoteAuthType != AuthType.ONLINE) { - if (this.remoteAuthType == AuthType.OFFLINE) { - geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.auth.login.offline")); - } else { - geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.auth.login.floodgate")); - } - authenticate(authData.name()); - } - } - public void authenticate(String username) { authenticate(username, ""); } @@ -1046,10 +1035,6 @@ public class GeyserSession implements GeyserConnection, CommandSender { closed = true; } - public void close() { - disconnect(GeyserLocale.getPlayerLocaleString("geyser.network.close", getClientData().getLanguageCode())); - } - /** * Executes a task and prints a stack trace if an error occurs. */ diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginDisconnectTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginDisconnectTranslator.java index 2f6674727..981fa83bf 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginDisconnectTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginDisconnectTranslator.java @@ -26,7 +26,13 @@ package org.geysermc.geyser.translator.protocol.java; import com.github.steveice10.mc.protocol.packet.login.clientbound.ClientboundLoginDisconnectPacket; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.TranslatableComponent; +import org.geysermc.common.PlatformType; +import org.geysermc.geyser.network.MinecraftProtocol; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; import org.geysermc.geyser.translator.text.MessageTranslator; @@ -36,8 +42,37 @@ public class JavaLoginDisconnectTranslator extends PacketTranslator Date: Fri, 18 Mar 2022 10:51:36 -0400 Subject: [PATCH 055/110] Update languages submodule --- core/src/main/resources/languages | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/languages b/core/src/main/resources/languages index d2a01218d..51e0ae2b5 160000 --- a/core/src/main/resources/languages +++ b/core/src/main/resources/languages @@ -1 +1 @@ -Subproject commit d2a01218d43f5b60bd4512d5eb6ad7e03a097f8c +Subproject commit 51e0ae2b527e3548ef82b65f33541f5eaeba2308 From 9c7210ef922090adaab19a2b28b367eec944a39e Mon Sep 17 00:00:00 2001 From: Jackson_57 <49173011+jackson-57@users.noreply.github.com> Date: Fri, 18 Mar 2022 16:01:14 -0400 Subject: [PATCH 056/110] Update wiki links (#2864) --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 885ec920b..23bde93d2 100644 --- a/README.md +++ b/README.md @@ -20,13 +20,13 @@ Special thanks to the DragonProxy project for being a trailblazer in protocol tr ### Currently supporting Minecraft Bedrock 1.17.41 + 1.18.0 - 1.18.10 and Minecraft Java 1.18.2. ## Setting Up -Take a look [here](https://github.com/GeyserMC/Geyser/wiki/Setup) for how to set up Geyser. +Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Geyser. [![YouTube Video](https://img.youtube.com/vi/U7dZZ8w7Gi4/0.jpg)](https://www.youtube.com/watch?v=U7dZZ8w7Gi4) ## Links: - Website: https://geysermc.org -- Docs: https://github.com/GeyserMC/Geyser/wiki +- Docs: https://wiki.geysermc.org/geyser/ - Download: https://ci.geysermc.org - Discord: https://discord.gg/geysermc - Donate: https://opencollective.com/geysermc @@ -39,7 +39,7 @@ Take a look [here](https://github.com/GeyserMC/Geyser/wiki/Setup) for how to set - Structure block UI ## What can't be fixed -There are a few things Geyser is unable to support due to various differences between Minecraft Bedrock and Java. For a list of these limitations, see the [Current Limitations](https://github.com/GeyserMC/Geyser/wiki/Current-Limitations) page. +There are a few things Geyser is unable to support due to various differences between Minecraft Bedrock and Java. For a list of these limitations, see the [Current Limitations](https://wiki.geysermc.org/geyser/current-limitations/) page. ## Compiling 1. Clone the repo to your computer From f8e983887e96fcdc36df54c22f090afec605ab37 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Fri, 18 Mar 2022 18:59:32 -0400 Subject: [PATCH 057/110] Add method in Connection API for transferring connections (#2891) --- .../org/geysermc/api/session/Connection.java | 11 ++++++++++- .../geysermc/geyser/session/GeyserSession.java | 16 ++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/api/base/src/main/java/org/geysermc/api/session/Connection.java b/api/base/src/main/java/org/geysermc/api/session/Connection.java index ccf3f7122..3e997912b 100644 --- a/api/base/src/main/java/org/geysermc/api/session/Connection.java +++ b/api/base/src/main/java/org/geysermc/api/session/Connection.java @@ -26,6 +26,7 @@ package org.geysermc.api.session; import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.common.value.qual.IntRange; import java.util.UUID; @@ -55,5 +56,13 @@ public interface Connection { */ String xuid(); - + /** + * Transfer the connection to a server. A Bedrock player can successfully transfer to the same server they are + * currently playing on. + * + * @param address The address of the server + * @param port The port of the server + * @return true if the transfer was a success + */ + boolean transfer(@NonNull String address, @IntRange(from = 0, to = 65535) int port); } diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index 8a030c385..1c25c2281 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -78,6 +78,7 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.NonNull; import lombok.Setter; +import org.checkerframework.common.value.qual.IntRange; import org.geysermc.common.PlatformType; import org.geysermc.cumulus.Form; import org.geysermc.cumulus.util.FormBuilder; @@ -1310,6 +1311,21 @@ public class GeyserSession implements GeyserConnection, CommandSender { return authData.xuid(); } + @SuppressWarnings("ConstantConditions") // Need to enforce the parameter annotations + @Override + public boolean transfer(@NonNull String address, @IntRange(from = 0, to = 65535) int port) { + if (address == null || address.isBlank()) { + throw new IllegalArgumentException("Server address cannot be null or blank"); + } else if (port < 0 || port > 65535) { + throw new IllegalArgumentException("Server port must be between 0 and 65535, was " + port); + } + TransferPacket transferPacket = new TransferPacket(); + transferPacket.setAddress(address); + transferPacket.setPort(port); + sendUpstreamPacket(transferPacket); + return true; + } + @Override public void sendMessage(String message) { TextPacket textPacket = new TextPacket(); From 732fd90d48ee94f97cecb40bc264da03c928cdbe Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 18 Mar 2022 23:31:25 -0400 Subject: [PATCH 058/110] Missed instance of Outdated server --- .../java/JavaLoginDisconnectTranslator.java | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginDisconnectTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginDisconnectTranslator.java index 981fa83bf..7fca689b0 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginDisconnectTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginDisconnectTranslator.java @@ -37,6 +37,8 @@ import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; import org.geysermc.geyser.translator.text.MessageTranslator; +import java.util.List; + @Translator(packet = ClientboundLoginDisconnectPacket.class) public class JavaLoginDisconnectTranslator extends PacketTranslator { @@ -44,7 +46,7 @@ public class JavaLoginDisconnectTranslator extends PacketTranslator children = component.children(); + for (int i = 0; i < children.size(); i++) { + if (children.get(i) instanceof TextComponent child && child.content().startsWith("Outdated server!")) { + // Reproduced on Paper 1.17.1 + isOutdatedMessage = true; + break; + } + } + } + } } String serverDisconnectMessage = MessageTranslator.convertMessage(disconnectReason, session.getLocale()); From b81ad3f0dbd6401319fde206019054265d155091 Mon Sep 17 00:00:00 2001 From: Hancho1577 Date: Sat, 19 Mar 2022 20:45:19 +0700 Subject: [PATCH 059/110] Prevent async task pool from being full (#2894) Fixes #2883 --- core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java | 2 ++ core/src/main/java/org/geysermc/geyser/util/WebUtils.java | 2 ++ 2 files changed, 4 insertions(+) diff --git a/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java index 4383dc4e9..282f6875a 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java @@ -601,6 +601,8 @@ public class SkinProvider { HttpURLConnection con = (HttpURLConnection) new URL(imageUrl).openConnection(); con.setRequestProperty("User-Agent", "Geyser-" + GeyserImpl.getInstance().getPlatformType().toString() + "/" + GeyserImpl.VERSION); + con.setConnectTimeout(10000); + con.setReadTimeout(10000); BufferedImage image = ImageIO.read(con.getInputStream()); if (image == null) throw new NullPointerException(); diff --git a/core/src/main/java/org/geysermc/geyser/util/WebUtils.java b/core/src/main/java/org/geysermc/geyser/util/WebUtils.java index 40daf22c7..fe479363f 100644 --- a/core/src/main/java/org/geysermc/geyser/util/WebUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/WebUtils.java @@ -52,6 +52,8 @@ public class WebUtils { HttpURLConnection con = (HttpURLConnection) url.openConnection(); con.setRequestMethod("GET"); con.setRequestProperty("User-Agent", "Geyser-" + GeyserImpl.getInstance().getPlatformType().toString() + "/" + GeyserImpl.VERSION); // Otherwise Java 8 fails on checking updates + con.setConnectTimeout(10000); + con.setReadTimeout(10000); return connectionToString(con); } catch (Exception e) { From 87d70be10d97e3f6e119e11217ad0157ed66faf6 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Sat, 19 Mar 2022 20:56:34 -0400 Subject: [PATCH 060/110] Register `floodgate:transfer` plugin channel (#2896) * Register floodgate:transfer channel * Don't warn on unknown channel --- .../pluginmessage/PluginMessageChannels.java | 45 +++++++++++++++++++ .../geyser/skin/FloodgateSkinUploader.java | 5 +-- .../java/JavaCustomPayloadTranslator.java | 5 ++- .../protocol/java/JavaLoginTranslator.java | 3 +- .../geyser/util/PluginMessageUtils.java | 20 --------- 5 files changed, 52 insertions(+), 26 deletions(-) create mode 100644 common/src/main/java/org/geysermc/floodgate/pluginmessage/PluginMessageChannels.java diff --git a/common/src/main/java/org/geysermc/floodgate/pluginmessage/PluginMessageChannels.java b/common/src/main/java/org/geysermc/floodgate/pluginmessage/PluginMessageChannels.java new file mode 100644 index 000000000..37c5d4015 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/pluginmessage/PluginMessageChannels.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2019-2022 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.floodgate.pluginmessage; + +import com.google.common.base.Charsets; + +public final class PluginMessageChannels { + public static final String SKIN = "floodgate:skin"; + public static final String FORM = "floodgate:form"; + public static final String TRANSFER = "floodgate:transfer"; + + private static final byte[] FLOODGATE_REGISTER_DATA = String.join("\0", SKIN, FORM, TRANSFER).getBytes(Charsets.UTF_8); + + /** + * Get the prebuilt register data as a byte array + * + * @return the register data of the Floodgate channels + */ + public static byte[] getFloodgateRegisterData() { + return FLOODGATE_REGISTER_DATA; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/skin/FloodgateSkinUploader.java b/core/src/main/java/org/geysermc/geyser/skin/FloodgateSkinUploader.java index 37a263312..7a800890b 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/FloodgateSkinUploader.java +++ b/core/src/main/java/org/geysermc/geyser/skin/FloodgateSkinUploader.java @@ -30,6 +30,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.Getter; +import org.geysermc.floodgate.pluginmessage.PluginMessageChannels; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserLogger; import org.geysermc.geyser.session.GeyserSession; @@ -48,8 +49,6 @@ import java.util.List; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; -import static org.geysermc.geyser.util.PluginMessageUtils.getSkinChannel; - public final class FloodgateSkinUploader { private final ObjectMapper JACKSON = new ObjectMapper(); private final List skinQueue = new ArrayList<>(); @@ -126,7 +125,7 @@ public final class FloodgateSkinUploader { byte[] bytes = (value + '\0' + signature) .getBytes(StandardCharsets.UTF_8); - PluginMessageUtils.sendMessage(session, getSkinChannel(), bytes); + PluginMessageUtils.sendMessage(session, PluginMessageChannels.SKIN, bytes); } break; case LOG_MESSAGE: diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCustomPayloadTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCustomPayloadTranslator.java index 04151c07f..33fb4f15c 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCustomPayloadTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCustomPayloadTranslator.java @@ -29,6 +29,7 @@ import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundCu import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundCustomPayloadPacket; import com.google.common.base.Charsets; import com.nukkitx.protocol.bedrock.packet.TransferPacket; +import org.geysermc.floodgate.pluginmessage.PluginMessageChannels; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserLogger; import org.geysermc.geyser.session.auth.AuthType; @@ -54,7 +55,7 @@ public class JavaCustomPayloadTranslator extends PacketTranslator Date: Tue, 22 Mar 2022 23:01:00 -0400 Subject: [PATCH 061/110] Replace show coordinates string Use the one built into Bedrock for less maintenance. --- .../main/java/org/geysermc/geyser/util/SettingsUtils.java | 6 +++++- core/src/main/resources/languages | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/util/SettingsUtils.java b/core/src/main/java/org/geysermc/geyser/util/SettingsUtils.java index ea3412451..5e2fe9944 100644 --- a/core/src/main/java/org/geysermc/geyser/util/SettingsUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/SettingsUtils.java @@ -62,7 +62,7 @@ public class SettingsUtils { // Client can only see its coordinates if reducedDebugInfo is disabled and coordinates are enabled in geyser config. if (session.getPreferencesCache().isAllowShowCoordinates()) { - builder.toggle("geyser.settings.option.coordinates", session.getPreferencesCache().isPrefersShowCoordinates()); + builder.toggle("%createWorldScreen.showCoordinates", session.getPreferencesCache().isPrefersShowCoordinates()); } if (CooldownUtils.getDefaultShowCooldown() != CooldownUtils.CooldownType.DISABLED) { @@ -175,6 +175,10 @@ public class SettingsUtils { } private static String translateEntry(String key, String locale) { + if (key.startsWith("%")) { + // Bedrock will translate + return key; + } if (key.startsWith("geyser.")) { return GeyserLocale.getPlayerLocaleString(key, locale); } diff --git a/core/src/main/resources/languages b/core/src/main/resources/languages index 51e0ae2b5..f073cf2b9 160000 --- a/core/src/main/resources/languages +++ b/core/src/main/resources/languages @@ -1 +1 @@ -Subproject commit 51e0ae2b527e3548ef82b65f33541f5eaeba2308 +Subproject commit f073cf2b9e62d1a9da45ac23448d59ca71074339 From 780218d39d7074ef893ace6f30f2c66b4e95ab0d Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 22 Mar 2022 23:03:37 -0400 Subject: [PATCH 062/110] Consolidate NoteblockBlockEntityTranslator behavior It was only used in one place that could better use existing code. --- .../NoteblockBlockEntityTranslator.java | 48 ------------------- .../java/level/JavaBlockEventTranslator.java | 27 +++++------ 2 files changed, 13 insertions(+), 62 deletions(-) delete mode 100644 core/src/main/java/org/geysermc/geyser/translator/level/block/entity/NoteblockBlockEntityTranslator.java diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/NoteblockBlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/NoteblockBlockEntityTranslator.java deleted file mode 100644 index a345d8fdb..000000000 --- a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/NoteblockBlockEntityTranslator.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2019-2022 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.translator.level.block.entity; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; -import com.nukkitx.math.vector.Vector3i; -import com.nukkitx.protocol.bedrock.packet.BlockEventPacket; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.level.block.BlockStateValues; - -/** - * Does not implement BlockEntityTranslator because it's only a block entity in Bedrock - */ -public class NoteblockBlockEntityTranslator { - - public static void translate(GeyserSession session, Position position) { - int blockState = session.getGeyser().getWorldManager().getBlockAt(session, position); - BlockEventPacket blockEventPacket = new BlockEventPacket(); - blockEventPacket.setBlockPosition(Vector3i.from(position.getX(), position.getY(), position.getZ())); - blockEventPacket.setEventType(0); - blockEventPacket.setEventData(BlockStateValues.getNoteblockPitch(blockState)); - session.sendUpstreamPacket(blockEventPacket); - } - -} diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaBlockEventTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaBlockEventTranslator.java index c7553020b..6adf1e00f 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaBlockEventTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaBlockEventTranslator.java @@ -35,14 +35,13 @@ import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket; import com.nukkitx.protocol.bedrock.packet.BlockEventPacket; import it.unimi.dsi.fastutil.objects.Object2IntMaps; import org.geysermc.common.PlatformType; +import org.geysermc.geyser.level.block.BlockStateValues; +import org.geysermc.geyser.level.physics.Direction; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.cache.PistonCache; +import org.geysermc.geyser.translator.level.block.entity.PistonBlockEntity; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; -import org.geysermc.geyser.level.block.BlockStateValues; -import org.geysermc.geyser.translator.level.block.entity.NoteblockBlockEntityTranslator; -import org.geysermc.geyser.translator.level.block.entity.PistonBlockEntity; -import org.geysermc.geyser.level.physics.Direction; @Translator(packet = ClientboundBlockEventPacket.class) public class JavaBlockEventTranslator extends PacketTranslator { @@ -50,8 +49,9 @@ public class JavaBlockEventTranslator extends PacketTranslator 0 ? 1 : 0); @@ -60,11 +60,12 @@ public class JavaBlockEventTranslator extends PacketTranslator new PistonBlockEntity(session, pos, direction, true, true)); + PistonBlockEntity blockEntity = pistonCache.getPistons().computeIfAbsent(vector, pos -> new PistonBlockEntity(session, pos, direction, true, true)); if (blockEntity.getAction() != action) { blockEntity.setAction(action, Object2IntMaps.emptyMap()); } } } else { - PistonBlockEntity blockEntity = pistonCache.getPistons().computeIfAbsent(position, pos -> { + PistonBlockEntity blockEntity = pistonCache.getPistons().computeIfAbsent(vector, pos -> { int blockId = session.getGeyser().getWorldManager().getBlockAt(session, position); boolean sticky = BlockStateValues.isStickyPiston(blockId); boolean extended = action != PistonValueType.PUSHING; @@ -106,10 +107,8 @@ public class JavaBlockEventTranslator extends PacketTranslator Date: Wed, 23 Mar 2022 13:57:25 -0400 Subject: [PATCH 063/110] Change banner item translator into NBT-specific translator Since we don't need to change any other item properties, this removes a builder hack that had to be implemented. --- .../inventory/LoomInventoryTranslator.java | 6 +- .../inventory/item/ItemTranslator.java | 7 +- .../item/NbtItemStackTranslator.java | 4 +- .../item/{ => nbt}/BannerTranslator.java | 100 +++++++----------- .../entity/BannerBlockEntityTranslator.java | 11 +- 5 files changed, 53 insertions(+), 75 deletions(-) rename core/src/main/java/org/geysermc/geyser/translator/inventory/item/{ => nbt}/BannerTranslator.java (70%) diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/LoomInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/LoomInventoryTranslator.java index a862a7e0d..a7b736d72 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/LoomInventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/LoomInventoryTranslator.java @@ -41,13 +41,13 @@ import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequ import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import org.geysermc.geyser.inventory.BedrockContainerSlot; import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.inventory.Inventory; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.inventory.BedrockContainerSlot; import org.geysermc.geyser.inventory.SlotType; import org.geysermc.geyser.inventory.updater.UIInventoryUpdater; -import org.geysermc.geyser.translator.inventory.item.BannerTranslator; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.inventory.item.nbt.BannerTranslator; import java.util.Collections; import java.util.List; diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/ItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/ItemTranslator.java index b8a7b60e2..f8acc0973 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/ItemTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/ItemTranslator.java @@ -47,6 +47,7 @@ import org.geysermc.geyser.translator.text.MessageTranslator; import org.geysermc.geyser.util.FileUtils; import javax.annotation.Nonnull; +import java.lang.reflect.InvocationTargetException; import java.util.*; import java.util.stream.Collectors; @@ -71,11 +72,11 @@ public abstract class ItemTranslator { try { if (NbtItemStackTranslator.class.isAssignableFrom(clazz)) { - NbtItemStackTranslator nbtItemTranslator = (NbtItemStackTranslator) clazz.newInstance(); + NbtItemStackTranslator nbtItemTranslator = (NbtItemStackTranslator) clazz.getDeclaredConstructor().newInstance(); loadedNbtItemTranslators.put(nbtItemTranslator, priority); continue; } - ItemTranslator itemStackTranslator = (ItemTranslator) clazz.newInstance(); + ItemTranslator itemStackTranslator = (ItemTranslator) clazz.getDeclaredConstructor().newInstance(); List appliedItems = itemStackTranslator.getAppliedItems(); for (ItemMapping item : appliedItems) { ItemTranslator registered = ITEM_STACK_TRANSLATORS.get(item.getJavaId()); @@ -87,7 +88,7 @@ public abstract class ItemTranslator { } ITEM_STACK_TRANSLATORS.put(item.getJavaId(), itemStackTranslator); } - } catch (InstantiationException | IllegalAccessException e) { + } catch (InstantiationException | InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { GeyserImpl.getInstance().getLogger().error("Could not instantiate annotated item translator " + clazz.getCanonicalName()); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/NbtItemStackTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/NbtItemStackTranslator.java index 31b0aa70e..bfa7ebc2e 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/NbtItemStackTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/NbtItemStackTranslator.java @@ -29,12 +29,12 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.registry.type.ItemMapping; -public class NbtItemStackTranslator { +public abstract class NbtItemStackTranslator { /** * Translate the item NBT to Bedrock * @param session the client's current session - * @param itemTag the item's CompoundTag + * @param itemTag the item's CompoundTag (cloned from Geyser's cached copy) * @param mapping Geyser's item mapping */ public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemMapping mapping) { diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/BannerTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/BannerTranslator.java similarity index 70% rename from core/src/main/java/org/geysermc/geyser/translator/inventory/item/BannerTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/BannerTranslator.java index 15f7c57ce..3da157cfc 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/BannerTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/BannerTranslator.java @@ -23,19 +23,18 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.translator.inventory.item; +package org.geysermc.geyser.translator.inventory.item.nbt; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.github.steveice10.opennbt.tag.builtin.*; import com.nukkitx.nbt.NbtList; import com.nukkitx.nbt.NbtMap; -import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.nbt.NbtType; -import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import org.geysermc.geyser.network.MinecraftProtocol; import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.type.ItemMapping; -import org.geysermc.geyser.registry.type.ItemMappings; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.inventory.item.ItemRemapper; +import org.geysermc.geyser.translator.inventory.item.NbtItemStackTranslator; import javax.annotation.Nonnull; import java.util.ArrayList; @@ -45,7 +44,7 @@ import java.util.Map; import java.util.stream.Collectors; @ItemRemapper -public class BannerTranslator extends ItemTranslator { +public class BannerTranslator extends NbtItemStackTranslator { /** * Holds what a Java ominous banner pattern looks like. * @@ -117,21 +116,6 @@ public class BannerTranslator extends ItemTranslator { .build(); } - /** - * Convert a list of patterns from Bedrock nbt to Java nbt - * - * @param patterns The patterns to convert - * @return The new converted patterns - */ - public static ListTag convertBannerPattern(List patterns) { - List tagsList = new ArrayList<>(); - for (NbtMap patternTag : patterns) { - tagsList.add(getJavaBannerPattern(patternTag)); - } - - return new ListTag("Patterns", tagsList); - } - /** * Convert the Bedrock edition banner pattern nbt to Java edition * @@ -146,62 +130,54 @@ public class BannerTranslator extends ItemTranslator { return new CompoundTag("", tags); } - @Override - protected ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping mapping, ItemMappings mappings) { - if (itemStack.getNbt() == null) { - return super.translateToBedrock(itemStack, mapping, mappings); + /** + * Convert a list of patterns from Java nbt to Bedrock nbt, or vice versa (we just need to invert the color) + * + * @param patterns The patterns to convert + */ + private void invertBannerColors(ListTag patterns) { + for (Tag patternTag : patterns.getValue()) { + IntTag color = ((CompoundTag) patternTag).get("Color"); + color.setValue(15 - color.getValue()); } - - ItemData.Builder builder = super.translateToBedrock(itemStack, mapping, mappings); - - CompoundTag blockEntityTag = itemStack.getNbt().get("BlockEntityTag"); - if (blockEntityTag != null && blockEntityTag.get("Patterns") instanceof ListTag patterns) { - NbtMapBuilder nbtBuilder = builder.build().getTag().toBuilder(); //TODO fix ugly hack - if (patterns.equals(OMINOUS_BANNER_PATTERN)) { - // Remove the current patterns and set the ominous banner type - nbtBuilder.remove("Patterns"); - nbtBuilder.putInt("Type", 1); - } else { - nbtBuilder.put("Patterns", convertBannerPattern(patterns)); - } - - builder.tag(nbtBuilder.build()); - } - - return builder; } @Override - public ItemStack translateToJava(ItemData itemData, ItemMapping mapping, ItemMappings mappings) { - if (itemData.getTag() == null) { - return super.translateToJava(itemData, mapping, mappings); + public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemMapping mapping) { + CompoundTag blockEntityTag = itemTag.get("BlockEntityTag"); + if (blockEntityTag != null && blockEntityTag.get("Patterns") instanceof ListTag patterns) { + if (patterns.equals(OMINOUS_BANNER_PATTERN)) { + // Remove the current patterns and set the ominous banner type + itemTag.put(new IntTag("Type", 1)); + } else { + invertBannerColors(patterns); + itemTag.put(patterns); + } + itemTag.remove("BlockEntityTag"); } + } - ItemStack itemStack = super.translateToJava(itemData, mapping, mappings); - - NbtMap nbtTag = itemData.getTag(); - if (nbtTag.containsKey("Type", NbtType.INT) && nbtTag.getInt("Type") == 1) { + @Override + public void translateToJava(CompoundTag itemTag, ItemMapping mapping) { + if (itemTag.get("Type") instanceof IntTag type && type.getValue() == 1) { // Ominous banner pattern - itemStack.getNbt().remove("Type"); + itemTag.remove("Type"); CompoundTag blockEntityTag = new CompoundTag("BlockEntityTag"); blockEntityTag.put(OMINOUS_BANNER_PATTERN); - itemStack.getNbt().put(blockEntityTag); - } else if (nbtTag.containsKey("Patterns", NbtType.LIST)) { - List patterns = nbtTag.getList("Patterns", NbtType.COMPOUND); - + itemTag.put(blockEntityTag); + } else if (itemTag.get("Patterns") instanceof ListTag patterns) { CompoundTag blockEntityTag = new CompoundTag("BlockEntityTag"); - blockEntityTag.put(convertBannerPattern(patterns)); + invertBannerColors(patterns); + blockEntityTag.put(patterns); - itemStack.getNbt().put(blockEntityTag); - itemStack.getNbt().remove("Patterns"); // Remove the old Bedrock patterns list + itemTag.put(blockEntityTag); + itemTag.remove("Patterns"); // Remove the old Bedrock patterns list } - - return itemStack; } @Override - public List getAppliedItems() { - return appliedItems; + public boolean acceptItem(ItemMapping mapping) { + return appliedItems.contains(mapping); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/BannerBlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/BannerBlockEntityTranslator.java index cca103cb3..725a17e7a 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/BannerBlockEntityTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/BannerBlockEntityTranslator.java @@ -28,9 +28,10 @@ package org.geysermc.geyser.translator.level.block.entity; import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.ListTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; import com.nukkitx.nbt.NbtMapBuilder; -import org.geysermc.geyser.translator.inventory.item.BannerTranslator; import org.geysermc.geyser.level.block.BlockStateValues; +import org.geysermc.geyser.translator.inventory.item.nbt.BannerTranslator; @BlockEntity(type = BlockEntityType.BANNER) public class BannerBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { @@ -45,8 +46,7 @@ public class BannerBlockEntityTranslator extends BlockEntityTranslator implement return; } - if (tag.contains("Patterns")) { - ListTag patterns = tag.get("Patterns"); + if (tag.get("Patterns") instanceof ListTag patterns) { if (patterns.equals(BannerTranslator.OMINOUS_BANNER_PATTERN)) { // This is an ominous banner; don't try to translate the raw patterns (it doesn't translate correctly) // and tell the Bedrock client that this is an ominous banner @@ -56,8 +56,9 @@ public class BannerBlockEntityTranslator extends BlockEntityTranslator implement } } - if (tag.contains("CustomName")) { - builder.put("CustomName", tag.get("CustomName").getValue()); + Tag customName = tag.get("CustomName"); + if (customName != null) { + builder.put("CustomName", customName.getValue()); } } } From b7de1b668f394e4f84464bfa691d1bc53ad363cb Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 23 Mar 2022 13:57:58 -0400 Subject: [PATCH 064/110] Remove unused NibbleArray class --- .../geyser/level/chunk/NibbleArray.java | 92 ------------------- 1 file changed, 92 deletions(-) delete mode 100644 core/src/main/java/org/geysermc/geyser/level/chunk/NibbleArray.java diff --git a/core/src/main/java/org/geysermc/geyser/level/chunk/NibbleArray.java b/core/src/main/java/org/geysermc/geyser/level/chunk/NibbleArray.java deleted file mode 100644 index 6ee53f992..000000000 --- a/core/src/main/java/org/geysermc/geyser/level/chunk/NibbleArray.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (c) 2019-2022 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.level.chunk; - -import com.nukkitx.network.util.Preconditions; - -public class NibbleArray implements Cloneable { - - private final byte[] data; - - public NibbleArray(int length) { - data = new byte[length / 2]; - } - - public NibbleArray(byte[] array) { - data = array; - } - - public byte get(int index) { - Preconditions.checkElementIndex(index, data.length * 2); - byte val = data[index / 2]; - if ((index & 1) == 0) { - return (byte) (val & 0x0f); - } else { - return (byte) ((val & 0xf0) >>> 4); - } - } - - public void set(int index, byte value) { - Preconditions.checkArgument(value >= 0 && value < 16, "Nibbles must have a value between 0 and 15."); - Preconditions.checkElementIndex(index, data.length * 2); - value &= 0xf; - int half = index / 2; - byte previous = data[half]; - if ((index & 1) == 0) { - data[half] = (byte) (previous & 0xf0 | value); - } else { - data[half] = (byte) (previous & 0x0f | value << 4); - } - } - - public void fill(byte value) { - Preconditions.checkArgument(value >= 0 && value < 16, "Nibbles must have a value between 0 and 15."); - value &= 0xf; - for (int i = 0; i < data.length; i++) { - data[i] = (byte) ((value << 4) | value); - } - } - - public void copyFrom(byte[] bytes) { - Preconditions.checkNotNull(bytes, "bytes"); - Preconditions.checkArgument(bytes.length == data.length, "length of provided byte array is %s but expected %s", bytes.length, - data.length); - System.arraycopy(bytes, 0, data, 0, data.length); - } - - public void copyFrom(NibbleArray array) { - Preconditions.checkNotNull(array, "array"); - copyFrom(array.data); - } - - public byte[] getData() { - return data; - } - - public NibbleArray copy() { - return new NibbleArray(getData().clone()); - } -} From 877301a500fae74dc2c9bcc1857b2a0718f65a3e Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 23 Mar 2022 16:21:04 -0400 Subject: [PATCH 065/110] Remove locator map from creative menu; show some map colors Java allows any map color but Bedrock only allows a few, so we take what we can get. Fixes #2617 --- .../populator/ItemRegistryPopulator.java | 3 + .../inventory/item/CompassTranslator.java | 18 ++--- .../inventory/item/FilledMapTranslator.java | 68 +++++++++++++++++++ .../inventory/item/ItemTranslator.java | 7 +- .../inventory/item/PotionTranslator.java | 18 ++--- .../inventory/item/TippedArrowTranslator.java | 21 ++---- 6 files changed, 95 insertions(+), 40 deletions(-) create mode 100644 core/src/main/java/org/geysermc/geyser/translator/inventory/item/FilledMapTranslator.java diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java index 9614e9da8..534c68776 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java @@ -166,6 +166,9 @@ public class ItemRegistryPopulator { if (identifier.equals("minecraft:debug_stick")) { // Just shows an empty texture; either way it doesn't exist in the creative menu on Java continue; + } else if (identifier.equals("minecraft:empty_map") && damage == 2) { + // Bedrock-only as its own item + continue; } StartGamePacket.ItemEntry entry = entries.get(identifier); int id = -1; diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/CompassTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/CompassTranslator.java index 9637f1aa9..b8ef85f81 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/CompassTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/CompassTranslator.java @@ -41,17 +41,6 @@ import java.util.stream.Collectors; @ItemRemapper public class CompassTranslator extends ItemTranslator { - private final List appliedItems; - - public CompassTranslator() { - appliedItems = Registries.ITEMS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) - .getItems() - .values() - .stream() - .filter(entry -> entry.getJavaIdentifier().endsWith("compass")) - .collect(Collectors.toList()); - } - @Override protected ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping mapping, ItemMappings mappings) { if (isLodestoneCompass(itemStack.getNbt())) { @@ -89,6 +78,11 @@ public class CompassTranslator extends ItemTranslator { @Override public List getAppliedItems() { - return appliedItems; + return Registries.ITEMS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) + .getItems() + .values() + .stream() + .filter(entry -> entry.getJavaIdentifier().endsWith("compass")) + .collect(Collectors.toList()); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/FilledMapTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/FilledMapTranslator.java new file mode 100644 index 000000000..3dfa2d82f --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/FilledMapTranslator.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2019-2022 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.translator.inventory.item; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; +import com.nukkitx.protocol.bedrock.data.inventory.ItemData; +import org.geysermc.geyser.network.MinecraftProtocol; +import org.geysermc.geyser.registry.Registries; +import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.registry.type.ItemMappings; + +import java.util.Collections; +import java.util.List; + +@ItemRemapper +public class FilledMapTranslator extends ItemTranslator { + + @Override + protected ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping mapping, ItemMappings mappings) { + ItemData.Builder builder = super.translateToBedrock(itemStack, mapping, mappings); + CompoundTag nbt = itemStack.getNbt(); + if (nbt != null && nbt.get("display") instanceof CompoundTag display) { + // Note: damage 5 treasure map, 6 ??? + Tag mapColor = display.get("MapColor"); + if (mapColor != null && mapColor.getValue() instanceof Number color) { + // Java Edition allows any color; Bedrock only allows some. So let's take what colors we can get + switch (color.intValue()) { + case 3830373 -> builder.damage(3); // Ocean Monument + case 5393476 -> builder.damage(4); // Woodland explorer + } + } + } + return builder; + } + + @Override + public List getAppliedItems() { + return Collections.singletonList( + Registries.ITEMS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) + .getMapping("minecraft:filled_map") + ); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/ItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/ItemTranslator.java index f8acc0973..539d20207 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/ItemTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/ItemTranslator.java @@ -303,13 +303,16 @@ public abstract class ItemTranslator { return new ItemStack(mapping.getJavaId(), itemData.getCount(), this.translateToJavaNBT("", itemData.getTag())); } + /** + * Used for initialization only and only called once. + */ public abstract List getAppliedItems(); protected ItemMapping getItemMapping(int javaId, CompoundTag nbt, ItemMappings mappings) { return mappings.getMapping(javaId); } - public NbtMap translateNbtToBedrock(CompoundTag tag) { + protected NbtMap translateNbtToBedrock(CompoundTag tag) { NbtMapBuilder builder = NbtMap.builder(); if (tag.getValue() != null && !tag.getValue().isEmpty()) { for (String str : tag.getValue().keySet()) { @@ -388,7 +391,7 @@ public abstract class ItemTranslator { return null; } - public CompoundTag translateToJavaNBT(String name, NbtMap tag) { + private CompoundTag translateToJavaNBT(String name, NbtMap tag) { CompoundTag javaTag = new CompoundTag(name); Map javaValue = javaTag.getValue(); if (tag != null && !tag.isEmpty()) { diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/PotionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/PotionTranslator.java index 54a6deadb..04183e095 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/PotionTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/PotionTranslator.java @@ -42,17 +42,6 @@ import java.util.stream.Collectors; @ItemRemapper public class PotionTranslator extends ItemTranslator { - private final List appliedItems; - - public PotionTranslator() { - appliedItems = Registries.ITEMS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) - .getItems() - .values() - .stream() - .filter(entry -> entry.getJavaIdentifier().endsWith("potion")) - .collect(Collectors.toList()); - } - @Override protected ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping mapping, ItemMappings mappings) { if (itemStack.getNbt() == null) return super.translateToBedrock(itemStack, mapping, mappings); @@ -84,6 +73,11 @@ public class PotionTranslator extends ItemTranslator { @Override public List getAppliedItems() { - return appliedItems; + return Registries.ITEMS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) + .getItems() + .values() + .stream() + .filter(entry -> entry.getJavaIdentifier().endsWith("potion")) + .collect(Collectors.toList()); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/TippedArrowTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/TippedArrowTranslator.java index 35e8baa07..b3aecb668 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/TippedArrowTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/TippedArrowTranslator.java @@ -41,23 +41,10 @@ import java.util.stream.Collectors; @ItemRemapper public class TippedArrowTranslator extends ItemTranslator { - - private final List appliedItems; - private static final int TIPPED_ARROW_JAVA_ID = Registries.ITEMS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) .getMapping("minecraft:tipped_arrow") .getJavaId(); - public TippedArrowTranslator() { - appliedItems = Registries.ITEMS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) - .getItems() - .values() - .stream() - .filter(entry -> entry.getJavaIdentifier().contains("arrow") - && !entry.getJavaIdentifier().contains("spectral")) - .collect(Collectors.toList()); - } - @Override protected ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping mapping, ItemMappings mappings) { if (!mapping.getJavaIdentifier().equals("minecraft:tipped_arrow") || itemStack.getNbt() == null) { @@ -93,6 +80,12 @@ public class TippedArrowTranslator extends ItemTranslator { @Override public List getAppliedItems() { - return appliedItems; + return Registries.ITEMS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) + .getItems() + .values() + .stream() + .filter(entry -> entry.getJavaIdentifier().contains("arrow") + && !entry.getJavaIdentifier().contains("spectral")) + .collect(Collectors.toList()); } } From 80b6d14ceefdfe5ad41116a040993b636ff98cb9 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 24 Mar 2022 17:39:35 -0400 Subject: [PATCH 066/110] Spigot: enable command completions for /geyser --- bootstrap/spigot/pom.xml | 22 +++++ .../platform/spigot/GeyserSpigotPlugin.java | 29 ++++++- .../command/GeyserPaperCommandListener.java | 87 +++++++++++++++++++ .../command/GeyserSpigotCommandManager.java | 21 +++-- 4 files changed, 152 insertions(+), 7 deletions(-) create mode 100644 bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserPaperCommandListener.java diff --git a/bootstrap/spigot/pom.xml b/bootstrap/spigot/pom.xml index da8b184e9..26f9c7083 100644 --- a/bootstrap/spigot/pom.xml +++ b/bootstrap/spigot/pom.xml @@ -19,6 +19,11 @@ viaversion-repo https://repo.viaversion.com + + + minecraft-repo + https://libraries.minecraft.net/ + @@ -34,6 +39,12 @@ 1.18.1-R0.1-SNAPSHOT provided + + io.papermc.paper + paper-mojangapi + 1.18.1-R0.1-SNAPSHOT + provided + com.viaversion viaversion @@ -45,6 +56,12 @@ spigot-all 1.4-SNAPSHOT + + me.lucko + commodore + 1.13 + compile + ${outputName}-Spigot @@ -95,6 +112,10 @@ org.objectweb.asm org.geysermc.geyser.platform.spigot.shaded.asm + + me.lucko.commodore + org.geysermc.geyser.platform.spigot.shaded.commodore + @@ -118,6 +139,7 @@ io.netty:netty-codec-dns:* io.netty:netty-resolver-dns:* io.netty:netty-resolver-dns-native-macos:* + com.mojang:* diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java index aae6c599a..b09aafd24 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java @@ -25,11 +25,15 @@ package org.geysermc.geyser.platform.spigot; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.viaversion.viaversion.api.Via; import com.viaversion.viaversion.api.data.MappingData; import com.viaversion.viaversion.api.protocol.ProtocolPathEntry; import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; +import me.lucko.commodore.Commodore; +import me.lucko.commodore.CommodoreProvider; import org.bukkit.Bukkit; +import org.bukkit.command.PluginCommand; import org.bukkit.plugin.java.JavaPlugin; import org.geysermc.common.PlatformType; import org.geysermc.geyser.Constants; @@ -43,6 +47,7 @@ import org.geysermc.geyser.level.WorldManager; import org.geysermc.geyser.network.MinecraftProtocol; import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; import org.geysermc.geyser.ping.IGeyserPingPassthrough; +import org.geysermc.geyser.platform.spigot.command.GeyserPaperCommandListener; import org.geysermc.geyser.platform.spigot.command.GeyserSpigotCommandExecutor; import org.geysermc.geyser.platform.spigot.command.GeyserSpigotCommandManager; import org.geysermc.geyser.platform.spigot.command.SpigotCommandSender; @@ -234,7 +239,29 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { Bukkit.getServer().getPluginManager().registerEvents(new GeyserPistonListener(geyser, this.geyserWorldManager), this); - this.getCommand("geyser").setExecutor(new GeyserSpigotCommandExecutor(geyser)); + PluginCommand pluginCommand = this.getCommand("geyser"); + pluginCommand.setExecutor(new GeyserSpigotCommandExecutor(geyser)); + + boolean brigadierSupported = CommodoreProvider.isSupported(); + geyserLogger.debug("Brigadier supported? " + brigadierSupported); + if (brigadierSupported) { + // Enable command completions if supported + // This is beneficial because this is sent over the network and Bedrock can see it + Commodore commodore = CommodoreProvider.getCommodore(this); + LiteralArgumentBuilder builder = LiteralArgumentBuilder.literal("geyser"); + for (String command : geyserCommandManager.getCommands().keySet()) { + builder.then(LiteralArgumentBuilder.literal(command)); + } + commodore.register(pluginCommand, builder); + + try { + Class.forName("com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent"); + Bukkit.getServer().getPluginManager().registerEvents(new GeyserPaperCommandListener(), this); + geyserLogger.debug("Successfully registered AsyncPlayerSendCommandsEvent listener."); + } catch (ClassNotFoundException e) { + geyserLogger.debug("Not registering AsyncPlayerSendCommandsEvent listener."); + } + } // Check to ensure the current setup can support the protocol version Geyser uses GeyserSpigotVersionChecker.checkForSupportedProtocol(geyserLogger, isViaVersion); diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserPaperCommandListener.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserPaperCommandListener.java new file mode 100644 index 000000000..00c1ba58d --- /dev/null +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserPaperCommandListener.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2019-2022 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.platform.spigot.command; + +import com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent; +import com.mojang.brigadier.tree.CommandNode; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.command.GeyserCommand; + +import java.net.InetSocketAddress; +import java.util.Iterator; +import java.util.Map; + +public final class GeyserPaperCommandListener implements Listener { + + @EventHandler + @SuppressWarnings("deprecation") // Used to indicate an unstable event + public void onCommandSend(AsyncPlayerSendCommandsEvent event) { + // Documentation says to check (event.isAsynchronous() || !event.hasFiredAsync()), but as of Paper 1.18.2 + // event.hasFiredAsync is never true + if (event.isAsynchronous()) { + CommandNode geyserBrigadier = event.getCommandNode().getChild("geyser"); + if (geyserBrigadier != null) { + Player player = event.getPlayer(); + boolean isJavaPlayer = isProbablyJavaPlayer(player); + Map commands = GeyserImpl.getInstance().getCommandManager().getCommands(); + Iterator> it = geyserBrigadier.getChildren().iterator(); + + while (it.hasNext()) { + CommandNode subnode = it.next(); + GeyserCommand command = commands.get(subnode.getName()); + if (command != null) { + if ((command.isBedrockOnly() && isJavaPlayer) || !player.hasPermission(command.getPermission())) { + // Remove this from the node as we don't have permission to use it + it.remove(); + } + } + } + } + } + } + + /** + * This early on, there is a rare chance that Geyser has yet to process the connection. We'll try to minimize that + * chance, though. + */ + private boolean isProbablyJavaPlayer(Player player) { + if (GeyserImpl.getInstance().connectionByUuid(player.getUniqueId()) != null) { + // For sure this is a Bedrock player + return false; + } + + if (GeyserImpl.getInstance().getConfig().isUseDirectConnection()) { + InetSocketAddress address = player.getAddress(); + if (address != null) { + return address.getPort() != 0; + } + } + return true; + } +} diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserSpigotCommandManager.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserSpigotCommandManager.java index 103390ab8..6107d5b47 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserSpigotCommandManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserSpigotCommandManager.java @@ -26,6 +26,7 @@ package org.geysermc.geyser.platform.spigot.command; import org.bukkit.Bukkit; +import org.bukkit.Server; import org.bukkit.command.Command; import org.bukkit.command.CommandMap; import org.geysermc.geyser.GeyserImpl; @@ -35,16 +36,24 @@ import java.lang.reflect.Field; public class GeyserSpigotCommandManager extends CommandManager { - private static CommandMap COMMAND_MAP; + private static final CommandMap COMMAND_MAP; static { + CommandMap commandMap = null; try { - Field cmdMapField = Bukkit.getServer().getClass().getDeclaredField("commandMap"); - cmdMapField.setAccessible(true); - COMMAND_MAP = (CommandMap) cmdMapField.get(Bukkit.getServer()); - } catch (NoSuchFieldException | IllegalAccessException ex) { - ex.printStackTrace(); + // Paper-only + Server.class.getMethod("getCommandMap"); + commandMap = Bukkit.getServer().getCommandMap(); + } catch (NoSuchMethodException e) { + try { + Field cmdMapField = Bukkit.getServer().getClass().getDeclaredField("commandMap"); + cmdMapField.setAccessible(true); + commandMap = (CommandMap) cmdMapField.get(Bukkit.getServer()); + } catch (NoSuchFieldException | IllegalAccessException ex) { + ex.printStackTrace(); + } } + COMMAND_MAP = commandMap; } public GeyserSpigotCommandManager(GeyserImpl geyser) { From c610e98f4c510de3920e46c879677ee7e95ae8d4 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 25 Mar 2022 14:04:16 -0400 Subject: [PATCH 067/110] Spigot: fix loading on 1.12 --- .../platform/spigot/GeyserSpigotPlugin.java | 21 +------ .../command/GeyserBrigadierSupport.java | 61 +++++++++++++++++++ 2 files changed, 63 insertions(+), 19 deletions(-) create mode 100644 bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserBrigadierSupport.java diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java index b09aafd24..6e490bfca 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java @@ -25,12 +25,10 @@ package org.geysermc.geyser.platform.spigot; -import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.viaversion.viaversion.api.Via; import com.viaversion.viaversion.api.data.MappingData; import com.viaversion.viaversion.api.protocol.ProtocolPathEntry; import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; -import me.lucko.commodore.Commodore; import me.lucko.commodore.CommodoreProvider; import org.bukkit.Bukkit; import org.bukkit.command.PluginCommand; @@ -47,7 +45,7 @@ import org.geysermc.geyser.level.WorldManager; import org.geysermc.geyser.network.MinecraftProtocol; import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; import org.geysermc.geyser.ping.IGeyserPingPassthrough; -import org.geysermc.geyser.platform.spigot.command.GeyserPaperCommandListener; +import org.geysermc.geyser.platform.spigot.command.GeyserBrigadierSupport; import org.geysermc.geyser.platform.spigot.command.GeyserSpigotCommandExecutor; import org.geysermc.geyser.platform.spigot.command.GeyserSpigotCommandManager; import org.geysermc.geyser.platform.spigot.command.SpigotCommandSender; @@ -245,22 +243,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { boolean brigadierSupported = CommodoreProvider.isSupported(); geyserLogger.debug("Brigadier supported? " + brigadierSupported); if (brigadierSupported) { - // Enable command completions if supported - // This is beneficial because this is sent over the network and Bedrock can see it - Commodore commodore = CommodoreProvider.getCommodore(this); - LiteralArgumentBuilder builder = LiteralArgumentBuilder.literal("geyser"); - for (String command : geyserCommandManager.getCommands().keySet()) { - builder.then(LiteralArgumentBuilder.literal(command)); - } - commodore.register(pluginCommand, builder); - - try { - Class.forName("com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent"); - Bukkit.getServer().getPluginManager().registerEvents(new GeyserPaperCommandListener(), this); - geyserLogger.debug("Successfully registered AsyncPlayerSendCommandsEvent listener."); - } catch (ClassNotFoundException e) { - geyserLogger.debug("Not registering AsyncPlayerSendCommandsEvent listener."); - } + GeyserBrigadierSupport.loadBrigadier(this, pluginCommand); } // Check to ensure the current setup can support the protocol version Geyser uses diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserBrigadierSupport.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserBrigadierSupport.java new file mode 100644 index 000000000..61900174c --- /dev/null +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserBrigadierSupport.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2019-2022 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.platform.spigot.command; + +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import me.lucko.commodore.Commodore; +import me.lucko.commodore.CommodoreProvider; +import org.bukkit.Bukkit; +import org.bukkit.command.PluginCommand; +import org.geysermc.geyser.platform.spigot.GeyserSpigotPlugin; + +/** + * Needs to be a separate class so pre-1.13 loads correctly. + */ +public final class GeyserBrigadierSupport { + + public static void loadBrigadier(GeyserSpigotPlugin plugin, PluginCommand pluginCommand) { + // Enable command completions if supported + // This is beneficial because this is sent over the network and Bedrock can see it + Commodore commodore = CommodoreProvider.getCommodore(plugin); + LiteralArgumentBuilder builder = LiteralArgumentBuilder.literal("geyser"); + for (String command : plugin.getGeyserCommandManager().getCommands().keySet()) { + builder.then(LiteralArgumentBuilder.literal(command)); + } + commodore.register(pluginCommand, builder); + + try { + Class.forName("com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent"); + Bukkit.getServer().getPluginManager().registerEvents(new GeyserPaperCommandListener(), plugin); + plugin.getGeyserLogger().debug("Successfully registered AsyncPlayerSendCommandsEvent listener."); + } catch (ClassNotFoundException e) { + plugin.getGeyserLogger().debug("Not registering AsyncPlayerSendCommandsEvent listener."); + } + } + + private GeyserBrigadierSupport() { + } +} From f639be6362d68409b0b249810abefae2895f7ce7 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 25 Mar 2022 20:22:39 -0400 Subject: [PATCH 068/110] Better handling of fake cooldown Because of Bedrock limitations, if a player has text background opacity enabled, they'll see an empty section where the title is usually displayed as the fake cooldown is shown. This commit minimizes the time that is shown by clearing the text as soon as possible. Reference issue: https://github.com/GeyserMC/Geyser/issues/1710 This commit also removes starting the fake cooldown process if the client switches to an inventory slot with the same Java ID. --- .../geyser/session/cache/WorldCache.java | 67 ++++++++++++++++++- .../BedrockMobEquipmentTranslator.java | 21 ++++-- .../java/title/JavaClearTitlesTranslator.java | 5 +- .../title/JavaSetTitleTextTranslator.java | 2 + .../JavaSetTitlesAnimationTranslator.java | 11 ++- .../geysermc/geyser/util/CooldownUtils.java | 22 +++--- 6 files changed, 105 insertions(+), 23 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/WorldCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/WorldCache.java index aa4d10d04..0ef5427ea 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/WorldCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/WorldCache.java @@ -26,24 +26,36 @@ package org.geysermc.geyser.session.cache; import com.github.steveice10.mc.protocol.data.game.setting.Difficulty; +import com.nukkitx.protocol.bedrock.packet.SetTitlePacket; import lombok.Getter; import lombok.Setter; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.scoreboard.Scoreboard; import org.geysermc.geyser.scoreboard.ScoreboardUpdater.ScoreboardSession; +import org.geysermc.geyser.session.GeyserSession; -@Getter -public class WorldCache { +public final class WorldCache { private final GeyserSession session; + @Getter private final ScoreboardSession scoreboardSession; + @Getter private Scoreboard scoreboard; + @Getter @Setter private Difficulty difficulty = Difficulty.EASY; + /** + * Whether our cooldown changed the title time, and the true title times need to be re-sent. + */ + private boolean titleTimesNeedReset = false; + private int trueTitleFadeInTime; + private int trueTitleStayTime; + private int trueTitleFadeOutTime; + public WorldCache(GeyserSession session) { this.session = session; this.scoreboard = new Scoreboard(session); scoreboardSession = new ScoreboardSession(session); + resetTitleTimes(false); } public void removeScoreboard() { @@ -58,4 +70,53 @@ public class WorldCache { int pps = scoreboardSession.getPacketsPerSecond(); return Math.max(pps, pendingPps); } + + public void markTitleTimesAsIncorrect() { + titleTimesNeedReset = true; + } + + /** + * Store the true active title times. + */ + public void setTitleTimes(int fadeInTime, int stayTime, int fadeOutTime) { + trueTitleFadeInTime = fadeInTime; + trueTitleStayTime = stayTime; + trueTitleFadeOutTime = fadeOutTime; + } + + /** + * If needed, ensure that the Bedrock client will use the correct timings for titles. + */ + public void synchronizeCorrectTitleTimes() { + if (titleTimesNeedReset) { + forceSyncCorrectTitleTimes(); + titleTimesNeedReset = false; + } + } + + private void forceSyncCorrectTitleTimes() { + SetTitlePacket titlePacket = new SetTitlePacket(); + titlePacket.setType(SetTitlePacket.Type.TIMES); + titlePacket.setText(""); + titlePacket.setFadeInTime(trueTitleFadeInTime); + titlePacket.setStayTime(trueTitleStayTime); + titlePacket.setFadeOutTime(trueTitleFadeOutTime); + titlePacket.setPlatformOnlineId(""); + titlePacket.setXuid(""); + + session.sendUpstreamPacket(titlePacket); + } + + /** + * Reset the true active title times to the (Java Edition 1.18.2) defaults. + */ + public void resetTitleTimes(boolean clientSync) { + trueTitleFadeInTime = 10; + trueTitleStayTime = 70; + trueTitleFadeOutTime = 20; + + if (clientSync) { + forceSyncCorrectTitleTimes(); + } + } } \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockMobEquipmentTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockMobEquipmentTranslator.java index 5c551dce4..2bbae4a49 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockMobEquipmentTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockMobEquipmentTranslator.java @@ -30,6 +30,7 @@ import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.Server import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundUseItemPacket; import com.nukkitx.protocol.bedrock.data.inventory.ContainerId; import com.nukkitx.protocol.bedrock.packet.MobEquipmentPacket; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; @@ -42,8 +43,9 @@ public class BedrockMobEquipmentTranslator extends PacketTranslator 8 || - packet.getContainerId() != ContainerId.INVENTORY || session.getPlayerInventory().getHeldItemSlot() == packet.getHotbarSlot()) { + int newSlot = packet.getHotbarSlot(); + if (!session.isSpawned() || newSlot > 8 || packet.getContainerId() != ContainerId.INVENTORY + || session.getPlayerInventory().getHeldItemSlot() == newSlot) { // For the last condition - Don't update the slot if the slot is the same - not Java Edition behavior and messes with plugins such as Grief Prevention return; } @@ -51,12 +53,15 @@ public class BedrockMobEquipmentTranslator extends PacketTranslator 20) return; // 0.0 usually happens on login and causes issues with visuals; anything above 20 means a plugin like OldCombatMechanics is being used - // Needs to be sent or no subtitle packet is recognized by the client + // Set the times to stay a bit with no fade in nor out SetTitlePacket titlePacket = new SetTitlePacket(); + titlePacket.setType(SetTitlePacket.Type.TIMES); + titlePacket.setStayTime(1000); + titlePacket.setText(""); + titlePacket.setXuid(""); + titlePacket.setPlatformOnlineId(""); + session.sendUpstreamPacket(titlePacket); + + session.getWorldCache().markTitleTimesAsIncorrect(); + + // Needs to be sent or no subtitle packet is recognized by the client + titlePacket = new SetTitlePacket(); titlePacket.setType(SetTitlePacket.Type.TITLE); titlePacket.setText(" "); titlePacket.setXuid(""); @@ -85,9 +96,6 @@ public class CooldownUtils { titlePacket.setType(SetTitlePacket.Type.SUBTITLE); } titlePacket.setText(getTitle(session)); - titlePacket.setFadeInTime(0); - titlePacket.setFadeOutTime(5); - titlePacket.setStayTime(2); titlePacket.setXuid(""); titlePacket.setPlatformOnlineId(""); session.sendUpstreamPacket(titlePacket); @@ -96,11 +104,7 @@ public class CooldownUtils { computeCooldown(session, sessionPreference, lastHitTime), 50, TimeUnit.MILLISECONDS); // Updated per tick. 1000 divided by 20 ticks equals 50 } else { SetTitlePacket removeTitlePacket = new SetTitlePacket(); - if (sessionPreference == CooldownType.ACTIONBAR) { - removeTitlePacket.setType(SetTitlePacket.Type.ACTIONBAR); - } else { - removeTitlePacket.setType(SetTitlePacket.Type.SUBTITLE); - } + removeTitlePacket.setType(SetTitlePacket.Type.CLEAR); removeTitlePacket.setText(" "); removeTitlePacket.setXuid(""); removeTitlePacket.setPlatformOnlineId(""); From 238be40c6a9f2e0eeddf094a4519895a941b49ef Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 25 Mar 2022 20:30:33 -0400 Subject: [PATCH 069/110] No need to reset when a set times packet is just about to be sent --- .../java/org/geysermc/geyser/session/cache/WorldCache.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/WorldCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/WorldCache.java index 0ef5427ea..17679ad3e 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/WorldCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/WorldCache.java @@ -82,6 +82,8 @@ public final class WorldCache { trueTitleFadeInTime = fadeInTime; trueTitleStayTime = stayTime; trueTitleFadeOutTime = fadeOutTime; + // The translator will sync this for us + titleTimesNeedReset = false; } /** @@ -90,7 +92,6 @@ public final class WorldCache { public void synchronizeCorrectTitleTimes() { if (titleTimesNeedReset) { forceSyncCorrectTitleTimes(); - titleTimesNeedReset = false; } } @@ -105,6 +106,7 @@ public final class WorldCache { titlePacket.setXuid(""); session.sendUpstreamPacket(titlePacket); + titleTimesNeedReset = false; } /** From 08051edad1b3c389e26a05f03855d5d9f09e469f Mon Sep 17 00:00:00 2001 From: rtm516 Date: Sat, 26 Mar 2022 15:49:02 +0000 Subject: [PATCH 070/110] Update Jackson dependency --- core/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/pom.xml b/core/pom.xml index c5d80db8a..cba63d355 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -13,7 +13,7 @@ 4.9.3 8.5.2 - 2.12.4 + 2.13.2.1 4.1.66.Final From 0067ba5bb9654a07e9e16dfd1dabaa7c6350fa96 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Sat, 26 Mar 2022 15:56:36 +0000 Subject: [PATCH 071/110] Fix jackson versions causing build to fail --- core/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/pom.xml b/core/pom.xml index cba63d355..d40b7dcc4 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -13,7 +13,7 @@ 4.9.3 8.5.2 - 2.13.2.1 + 2.13.2 4.1.66.Final @@ -52,7 +52,7 @@ com.fasterxml.jackson.core jackson-databind - ${jackson.version} + ${jackson.version}.1 compile From f78d2d3d2a691dc5f4cbfbf05e75e017c379475f Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 29 Mar 2022 14:36:58 -0400 Subject: [PATCH 072/110] Fix ghost items when taking items out of a furnace --- .../java/org/geysermc/geyser/inventory/click/ClickPlan.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/geysermc/geyser/inventory/click/ClickPlan.java b/core/src/main/java/org/geysermc/geyser/inventory/click/ClickPlan.java index b0cca53d9..575294a5b 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/click/ClickPlan.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/click/ClickPlan.java @@ -152,6 +152,7 @@ public final class ClickPlan { clickedItemStack, changedItems ); + System.out.println(clickPacket); session.sendDownstreamPacket(clickPacket); } @@ -391,7 +392,7 @@ public final class ClickPlan { public IntSet getAffectedSlots() { IntSet affectedSlots = new IntOpenHashSet(); for (ClickAction action : plan) { - if (translator.getSlotType(action.slot) == SlotType.NORMAL && action.slot != Click.OUTSIDE_SLOT) { + if (translator.getSlotType(action.slot) != SlotType.OUTPUT && action.slot != Click.OUTSIDE_SLOT) { affectedSlots.add(action.slot); if (action.click.actionType == ContainerActionType.MOVE_TO_HOTBAR_SLOT) { //TODO won't work if offhand is added From 7a5321b78fc1af44e8e3b45960fcb67e9a168458 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 29 Mar 2022 14:38:15 -0400 Subject: [PATCH 073/110] Hmm what print line? --- .../main/java/org/geysermc/geyser/inventory/click/ClickPlan.java | 1 - 1 file changed, 1 deletion(-) diff --git a/core/src/main/java/org/geysermc/geyser/inventory/click/ClickPlan.java b/core/src/main/java/org/geysermc/geyser/inventory/click/ClickPlan.java index 575294a5b..ec36645da 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/click/ClickPlan.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/click/ClickPlan.java @@ -152,7 +152,6 @@ public final class ClickPlan { clickedItemStack, changedItems ); - System.out.println(clickPacket); session.sendDownstreamPacket(clickPacket); } From 2a05dd57ffe8208fcf0ed903baa379d3e415e557 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 30 Mar 2022 22:30:49 -0400 Subject: [PATCH 074/110] Don't store GameProfile class of players This stores repetitive information, and also we don't currently use the signature, so it's wasted memory. --- .../entity/type/player/PlayerEntity.java | 20 +++++++---- .../type/player/SessionPlayerEntity.java | 3 +- .../entity/type/player/SkullPlayerEntity.java | 7 ++-- .../geyser/skin/FakeHeadProvider.java | 18 ++-------- .../org/geysermc/geyser/skin/SkinManager.java | 36 +++++++++---------- .../geysermc/geyser/skin/SkinProvider.java | 12 ++----- .../geyser/skin/SkullSkinManager.java | 2 +- .../entity/SkullBlockEntityTranslator.java | 28 +++++---------- .../player/JavaPlayerInfoTranslator.java | 18 +++++++--- .../entity/spawn/JavaAddPlayerTranslator.java | 4 ++- 10 files changed, 68 insertions(+), 80 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java index 70b5ede99..58f04a756 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java @@ -25,7 +25,6 @@ package org.geysermc.geyser.entity.type.player; -import com.github.steveice10.mc.auth.data.GameProfile; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; @@ -61,15 +60,21 @@ import org.geysermc.geyser.translator.text.MessageTranslator; import javax.annotation.Nullable; import java.util.Collections; import java.util.Optional; +import java.util.UUID; import java.util.concurrent.TimeUnit; @Getter @Setter public class PlayerEntity extends LivingEntity { public static final float SNEAKING_POSE_HEIGHT = 1.5f; - private GameProfile profile; private String username; - private boolean playerList = true; // Player is in the player list + private boolean playerList = true; // Player is in the player list + + /** + * The textures property from the GameProfile. + */ + @Nullable + private String texturesProperty; private Vector3i bedPosition; @@ -82,11 +87,12 @@ public class PlayerEntity extends LivingEntity { */ private ParrotEntity rightParrot; - public PlayerEntity(GeyserSession session, int entityId, long geyserId, GameProfile gameProfile, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { - super(session, entityId, geyserId, gameProfile.getId(), EntityDefinitions.PLAYER, position, motion, yaw, pitch, headYaw); + public PlayerEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, Vector3f position, + Vector3f motion, float yaw, float pitch, float headYaw, String username, @Nullable String texturesProperty) { + super(session, entityId, geyserId, uuid, EntityDefinitions.PLAYER, position, motion, yaw, pitch, headYaw); - profile = gameProfile; - username = gameProfile.getName(); + this.username = username; + this.texturesProperty = texturesProperty; } @Override diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java index 077f82171..ae8d23810 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java @@ -25,7 +25,6 @@ package org.geysermc.geyser.entity.type.player; -import com.github.steveice10.mc.auth.data.GameProfile; import com.github.steveice10.mc.protocol.data.game.entity.attribute.Attribute; import com.github.steveice10.mc.protocol.data.game.entity.attribute.AttributeType; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose; @@ -71,7 +70,7 @@ public class SessionPlayerEntity extends PlayerEntity { private int fakeTradeXp; public SessionPlayerEntity(GeyserSession session) { - super(session, -1, 1, new GameProfile(UUID.randomUUID(), "unknown"), Vector3f.ZERO, Vector3f.ZERO, 0, 0, 0); + super(session, -1, 1, UUID.randomUUID(), Vector3f.ZERO, Vector3f.ZERO, 0, 0, 0, "unknown", null); valid = true; } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/SkullPlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/SkullPlayerEntity.java index 847abf2a9..ce1615816 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/SkullPlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/SkullPlayerEntity.java @@ -25,7 +25,6 @@ package org.geysermc.geyser.entity.type.player; -import com.github.steveice10.mc.auth.data.GameProfile; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.PlayerPermission; @@ -36,6 +35,8 @@ import com.nukkitx.protocol.bedrock.packet.AddPlayerPacket; import lombok.Getter; import org.geysermc.geyser.session.GeyserSession; +import java.util.UUID; + /** * A wrapper to handle skulls more effectively - skulls have to be treated as entities since there are no * custom player skulls in Bedrock. @@ -48,8 +49,8 @@ public class SkullPlayerEntity extends PlayerEntity { @Getter private final int blockState; - public SkullPlayerEntity(GeyserSession session, long geyserId, GameProfile gameProfile, Vector3f position, float rotation, int blockState) { - super(session, 0, geyserId, gameProfile, position, Vector3f.ZERO, rotation, 0, rotation); + public SkullPlayerEntity(GeyserSession session, long geyserId, Vector3f position, float rotation, int blockState, String texturesProperty) { + super(session, 0, geyserId, UUID.randomUUID(), position, Vector3f.ZERO, rotation, 0, rotation, "", texturesProperty); this.blockState = blockState; setPlayerList(false); } diff --git a/core/src/main/java/org/geysermc/geyser/skin/FakeHeadProvider.java b/core/src/main/java/org/geysermc/geyser/skin/FakeHeadProvider.java index 66354b494..6794af498 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/FakeHeadProvider.java +++ b/core/src/main/java/org/geysermc/geyser/skin/FakeHeadProvider.java @@ -25,7 +25,6 @@ package org.geysermc.geyser.skin; -import com.github.steveice10.mc.auth.data.GameProfile; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; @@ -106,7 +105,7 @@ public class FakeHeadProvider { session.getPlayerWithCustomHeads().add(entity.getUuid()); - GameProfile.Property texturesProperty = entity.getProfile().getProperty("textures"); + String texturesProperty = entity.getTexturesProperty(); SkinProvider.EXECUTOR_SERVICE.execute(() -> { try { @@ -182,7 +181,7 @@ public class FakeHeadProvider { @Getter @Setter private static class FakeHeadEntry { - private final GameProfile.Property texturesProperty; + private final String texturesProperty; private final String fakeHeadSkinUrl; private PlayerEntity entity; @@ -192,18 +191,7 @@ public class FakeHeadProvider { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; FakeHeadEntry that = (FakeHeadEntry) o; - return equals(texturesProperty, that.texturesProperty) && Objects.equals(fakeHeadSkinUrl, that.fakeHeadSkinUrl); - } - - private boolean equals(GameProfile.Property a, GameProfile.Property b) { - //TODO actually fix this in MCAuthLib - if (a == b) { - return true; - } - if (a == null || b == null) { - return false; - } - return Objects.equals(a.getName(), b.getName()) && Objects.equals(a.getValue(), b.getValue()) && Objects.equals(a.getSignature(), b.getSignature()); + return Objects.equals(texturesProperty, that.texturesProperty) && Objects.equals(fakeHeadSkinUrl, that.fakeHeadSkinUrl); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java b/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java index 4269110f5..4eb92c3ac 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java @@ -26,7 +26,6 @@ package org.geysermc.geyser.skin; import com.fasterxml.jackson.databind.JsonNode; -import com.github.steveice10.mc.auth.data.GameProfile; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.ListTag; import com.github.steveice10.opennbt.tag.builtin.StringTag; @@ -34,9 +33,9 @@ import com.nukkitx.protocol.bedrock.data.skin.ImageData; import com.nukkitx.protocol.bedrock.data.skin.SerializedSkin; import com.nukkitx.protocol.bedrock.packet.PlayerListPacket; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.session.auth.AuthType; import org.geysermc.geyser.entity.type.player.PlayerEntity; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.session.auth.AuthType; import org.geysermc.geyser.session.auth.BedrockClientData; import org.geysermc.geyser.text.GeyserLocale; @@ -54,7 +53,7 @@ public class SkinManager { * Builds a Bedrock player list entry from our existing, cached Bedrock skin information */ public static PlayerListPacket.Entry buildCachedEntry(GeyserSession session, PlayerEntity playerEntity) { - GameProfileData data = GameProfileData.from(playerEntity.getProfile()); + GameProfileData data = GameProfileData.from(playerEntity); SkinProvider.Cape cape = SkinProvider.getCachedCape(data.capeUrl()); SkinProvider.SkinGeometry geometry = SkinProvider.SkinGeometry.getLegacy(data.isAlex()); @@ -65,8 +64,8 @@ public class SkinManager { return buildEntryManually( session, - playerEntity.getProfile().getId(), - playerEntity.getProfile().getName(), + playerEntity.getUuid(), + playerEntity.getUsername(), playerEntity.getGeyserId(), skin.getTextureUrl(), skin.getSkinData(), @@ -227,31 +226,31 @@ public class SkinManager { } /** - * Generate the GameProfileData from the given GameProfile + * Generate the GameProfileData from the given player entity * - * @param profile GameProfile to build the GameProfileData from + * @param entity entity to build the GameProfileData from * @return The built GameProfileData */ - public static GameProfileData from(GameProfile profile) { + public static GameProfileData from(PlayerEntity entity) { try { - GameProfile.Property skinProperty = profile.getProperty("textures"); + String texturesProperty = entity.getTexturesProperty(); - if (skinProperty == null) { + if (texturesProperty == null) { // Likely offline mode - return loadBedrockOrOfflineSkin(profile); + return loadBedrockOrOfflineSkin(entity); } - GameProfileData data = loadFromJson(skinProperty.getValue()); + GameProfileData data = loadFromJson(texturesProperty); if (data != null) { return data; } else { - return loadBedrockOrOfflineSkin(profile); + return loadBedrockOrOfflineSkin(entity); } } catch (IOException exception) { - GeyserImpl.getInstance().getLogger().debug("Something went wrong while processing skin for " + profile.getName()); + GeyserImpl.getInstance().getLogger().debug("Something went wrong while processing skin for " + entity.getUsername()); if (GeyserImpl.getInstance().getConfig().isDebugMode()) { exception.printStackTrace(); } - return loadBedrockOrOfflineSkin(profile); + return loadBedrockOrOfflineSkin(entity); } } @@ -280,14 +279,15 @@ public class SkinManager { * @return default skin with default cape when texture data is invalid, or the Bedrock player's skin if this * is a Bedrock player. */ - private static GameProfileData loadBedrockOrOfflineSkin(GameProfile profile) { + private static GameProfileData loadBedrockOrOfflineSkin(PlayerEntity entity) { // Fallback to the offline mode of working it out - boolean isAlex = (Math.abs(profile.getId().hashCode() % 2) == 1); + UUID uuid = entity.getUuid(); + boolean isAlex = (Math.abs(uuid.hashCode() % 2) == 1); String skinUrl = isAlex ? SkinProvider.EMPTY_SKIN_ALEX.getTextureUrl() : SkinProvider.EMPTY_SKIN.getTextureUrl(); String capeUrl = SkinProvider.EMPTY_CAPE.getTextureUrl(); if (("steve".equals(skinUrl) || "alex".equals(skinUrl)) && GeyserImpl.getInstance().getConfig().getRemote().getAuthType() != AuthType.ONLINE) { - GeyserSession session = GeyserImpl.getInstance().connectionByUuid(profile.getId()); + GeyserSession session = GeyserImpl.getInstance().connectionByUuid(uuid); if (session != null) { skinUrl = session.getClientData().getSkinId(); diff --git a/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java index 282f6875a..43cf30b47 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java @@ -27,7 +27,6 @@ package org.geysermc.geyser.skin; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.github.steveice10.mc.auth.data.GameProfile; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.IntArrayTag; import com.github.steveice10.opennbt.tag.builtin.Tag; @@ -53,7 +52,6 @@ import java.io.IOException; import java.net.HttpURLConnection; import java.net.URL; import java.nio.charset.StandardCharsets; -import java.util.List; import java.util.*; import java.util.concurrent.*; import java.util.function.Predicate; @@ -157,7 +155,7 @@ public class SkinProvider { } public static CompletableFuture requestSkinData(PlayerEntity entity) { - SkinManager.GameProfileData data = SkinManager.GameProfileData.from(entity.getProfile()); + SkinManager.GameProfileData data = SkinManager.GameProfileData.from(entity); return requestSkinAndCape(entity.getUuid(), data.skinUrl(), data.capeUrl()) .thenApplyAsync(skinAndCape -> { @@ -546,12 +544,11 @@ public class SkinProvider { * @param skullOwner the CompoundTag of the skull with no textures * @return a completable GameProfile with textures included */ - public static CompletableFuture requestTexturesFromUsername(CompoundTag skullOwner) { + public static CompletableFuture requestTexturesFromUsername(CompoundTag skullOwner) { return CompletableFuture.supplyAsync(() -> { Tag uuidTag = skullOwner.get("Id"); String uuidToString = ""; JsonNode node; - GameProfile gameProfile = new GameProfile(UUID.randomUUID(), ""); boolean retrieveUuidFromInternet = !(uuidTag instanceof IntArrayTag); // also covers null check if (!retrieveUuidFromInternet) { @@ -577,15 +574,12 @@ public class SkinProvider { // Get textures from UUID node = WebUtils.getJson("https://sessionserver.mojang.com/session/minecraft/profile/" + uuidToString); - List profileProperties = new ArrayList<>(); JsonNode properties = node.get("properties"); if (properties == null) { GeyserImpl.getInstance().getLogger().debug("No properties found in Mojang response for " + uuidToString); return null; } - profileProperties.add(new GameProfile.Property("textures", node.get("properties").get(0).get("value").asText())); - gameProfile.setProperties(profileProperties); - return gameProfile; + return node.get("properties").get(0).get("value").asText(); } catch (Exception e) { if (GeyserImpl.getInstance().getConfig().isDebugMode()) { e.printStackTrace(); diff --git a/core/src/main/java/org/geysermc/geyser/skin/SkullSkinManager.java b/core/src/main/java/org/geysermc/geyser/skin/SkullSkinManager.java index 009d98c87..58054e9c5 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkullSkinManager.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkullSkinManager.java @@ -50,7 +50,7 @@ public class SkullSkinManager extends SkinManager { public static void requestAndHandleSkin(PlayerEntity entity, GeyserSession session, Consumer skinConsumer) { - GameProfileData data = GameProfileData.from(entity.getProfile()); + GameProfileData data = GameProfileData.from(entity); SkinProvider.requestSkin(entity.getUuid(), data.skinUrl(), true) .whenCompleteAsync((skin, throwable) -> { diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SkullBlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SkullBlockEntityTranslator.java index 8fc8732f7..50d79c10f 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SkullBlockEntityTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SkullBlockEntityTranslator.java @@ -25,7 +25,6 @@ package org.geysermc.geyser.translator.level.block.entity; -import com.github.steveice10.mc.auth.data.GameProfile; import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.ListTag; @@ -35,15 +34,12 @@ import com.nukkitx.math.vector.Vector3i; import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.type.player.SkullPlayerEntity; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.level.block.BlockStateValues; +import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.skin.SkinProvider; import org.geysermc.geyser.skin.SkullSkinManager; -import java.util.ArrayList; import java.util.LinkedHashMap; -import java.util.List; -import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; @@ -62,7 +58,7 @@ public class SkullBlockEntityTranslator extends BlockEntityTranslator implements builder.put("SkullType", skullVariant); } - public static CompletableFuture getProfile(CompoundTag tag) { + private static CompletableFuture getTextures(CompoundTag tag) { CompoundTag owner = tag.get("SkullOwner"); if (owner != null) { CompoundTag properties = owner.get("Properties"); @@ -73,13 +69,7 @@ public class SkullBlockEntityTranslator extends BlockEntityTranslator implements ListTag textures = properties.get("textures"); LinkedHashMap tag1 = (LinkedHashMap) textures.get(0).getValue(); StringTag texture = (StringTag) tag1.get("Value"); - - List profileProperties = new ArrayList<>(); - - GameProfile gameProfile = new GameProfile(UUID.randomUUID(), ""); - profileProperties.add(new GameProfile.Property("textures", texture.getValue())); - gameProfile.setProperties(profileProperties); - return CompletableFuture.completedFuture(gameProfile); + return CompletableFuture.completedFuture(texture.getValue()); } return CompletableFuture.completedFuture(null); } @@ -108,21 +98,21 @@ public class SkullBlockEntityTranslator extends BlockEntityTranslator implements Vector3i blockPosition = Vector3i.from(posX, posY, posZ); Vector3f entityPosition = Vector3f.from(x, y, z); - getProfile(tag).whenComplete((gameProfile, throwable) -> { - if (gameProfile == null) { + getTextures(tag).whenComplete((texturesProperty, throwable) -> { + if (texturesProperty == null) { session.getGeyser().getLogger().debug("Custom skull with invalid SkullOwner tag: " + blockPosition + " " + tag); return; } if (session.getEventLoop().inEventLoop()) { - spawnPlayer(session, gameProfile, blockPosition, entityPosition, rotation, blockState); + spawnPlayer(session, texturesProperty, blockPosition, entityPosition, rotation, blockState); } else { - session.executeInEventLoop(() -> spawnPlayer(session, gameProfile, blockPosition, entityPosition, rotation, blockState)); + session.executeInEventLoop(() -> spawnPlayer(session, texturesProperty, blockPosition, entityPosition, rotation, blockState)); } }); } - private static void spawnPlayer(GeyserSession session, GameProfile profile, Vector3i blockPosition, + private static void spawnPlayer(GeyserSession session, String texturesProperty, Vector3i blockPosition, Vector3f entityPosition, float rotation, int blockState) { long geyserId = session.getEntityCache().getNextEntityId().incrementAndGet(); @@ -132,7 +122,7 @@ public class SkullBlockEntityTranslator extends BlockEntityTranslator implements existingSkull.despawnEntity(blockPosition); } - SkullPlayerEntity player = new SkullPlayerEntity(session, geyserId, profile, entityPosition, rotation, blockState); + SkullPlayerEntity player = new SkullPlayerEntity(session, geyserId, entityPosition, rotation, blockState, texturesProperty); // Cache entity session.getSkullCache().put(blockPosition, player); diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerInfoTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerInfoTranslator.java index fd9e5887d..993da7746 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerInfoTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerInfoTranslator.java @@ -25,6 +25,7 @@ package org.geysermc.geyser.translator.protocol.java.entity.player; +import com.github.steveice10.mc.auth.data.GameProfile; import com.github.steveice10.mc.protocol.data.game.PlayerListEntry; import com.github.steveice10.mc.protocol.data.game.PlayerListEntryAction; import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundPlayerInfoPacket; @@ -50,31 +51,38 @@ public class JavaPlayerInfoTranslator extends PacketTranslator { + GameProfile profile = entry.getProfile(); PlayerEntity playerEntity; - boolean self = entry.getProfile().getId().equals(session.getPlayerEntity().getUuid()); + boolean self = profile.getId().equals(session.getPlayerEntity().getUuid()); if (self) { // Entity is ourself playerEntity = session.getPlayerEntity(); } else { - playerEntity = session.getEntityCache().getPlayerEntity(entry.getProfile().getId()); + playerEntity = session.getEntityCache().getPlayerEntity(profile.getId()); } + GameProfile.Property textures = profile.getProperty("textures"); + String texturesProperty = textures == null ? null : textures.getValue(); + if (playerEntity == null) { // It's a new player playerEntity = new PlayerEntity( session, -1, session.getEntityCache().getNextEntityId().incrementAndGet(), - entry.getProfile(), + profile.getId(), Vector3f.ZERO, Vector3f.ZERO, - 0, 0, 0 + 0, 0, 0, + profile.getName(), + texturesProperty ); session.getEntityCache().addPlayerEntity(playerEntity); } else { - playerEntity.setProfile(entry.getProfile()); + playerEntity.setUsername(profile.getName()); + playerEntity.setTexturesProperty(texturesProperty); } playerEntity.setPlayerList(true); diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/spawn/JavaAddPlayerTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/spawn/JavaAddPlayerTranslator.java index f0b1b4874..c54b75f4f 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/spawn/JavaAddPlayerTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/spawn/JavaAddPlayerTranslator.java @@ -48,7 +48,9 @@ public class JavaAddPlayerTranslator extends PacketTranslator Date: Fri, 1 Apr 2022 15:20:30 -0400 Subject: [PATCH 075/110] Be more resilient with different enchantment NBT types Fixes #2911 --- .../item/nbt/EnchantmentTranslator.java | 9 ++++----- .../java/org/geysermc/geyser/util/ItemUtils.java | 16 +++++++++++----- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/EnchantmentTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/EnchantmentTranslator.java index 155435c79..847a70a27 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/EnchantmentTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/EnchantmentTranslator.java @@ -121,10 +121,6 @@ public class EnchantmentTranslator extends NbtItemStackTranslator { private CompoundTag remapEnchantment(CompoundTag tag) { - Tag javaEnchLvl = tag.get("lvl"); - if (!(javaEnchLvl instanceof ShortTag || javaEnchLvl instanceof IntTag)) - return null; - Tag javaEnchId = tag.get("id"); if (!(javaEnchId instanceof StringTag)) return null; @@ -135,9 +131,12 @@ public class EnchantmentTranslator extends NbtItemStackTranslator { return null; } + Tag javaEnchLvl = tag.get("lvl"); + CompoundTag bedrockTag = new CompoundTag(""); bedrockTag.put(new ShortTag("id", (short) enchantment.ordinal())); - bedrockTag.put(new ShortTag("lvl", ((Number) javaEnchLvl.getValue()).shortValue())); + // If the tag cannot parse, Java Edition 1.18.2 sets to 0 + bedrockTag.put(new ShortTag("lvl", javaEnchLvl != null && javaEnchLvl.getValue() instanceof Number lvl ? lvl.shortValue() : 0)); return bedrockTag; } diff --git a/core/src/main/java/org/geysermc/geyser/util/ItemUtils.java b/core/src/main/java/org/geysermc/geyser/util/ItemUtils.java index f05d702a0..37c4609fe 100644 --- a/core/src/main/java/org/geysermc/geyser/util/ItemUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/ItemUtils.java @@ -29,21 +29,27 @@ import com.github.steveice10.opennbt.tag.builtin.*; import it.unimi.dsi.fastutil.ints.Int2IntMap; import org.geysermc.geyser.session.GeyserSession; +import javax.annotation.Nullable; + public class ItemUtils { private static Int2IntMap DYE_COLORS = null; - public static int getEnchantmentLevel(CompoundTag itemNBTData, String enchantmentId) { - ListTag enchantments = (itemNBTData == null ? null : itemNBTData.get("Enchantments")); + public static int getEnchantmentLevel(@Nullable CompoundTag itemNBTData, String enchantmentId) { + if (itemNBTData == null) { + return 0; + } + ListTag enchantments = itemNBTData.get("Enchantments"); if (enchantments != null) { - int enchantmentLevel = 0; for (Tag tag : enchantments) { CompoundTag enchantment = (CompoundTag) tag; StringTag enchantId = enchantment.get("id"); if (enchantId.getValue().equals(enchantmentId)) { - enchantmentLevel = (int) ((ShortTag) enchantment.get("lvl")).getValue(); + Tag lvl = enchantment.get("lvl"); + if (lvl != null && lvl.getValue() instanceof Number number) { + return number.intValue(); + } } } - return enchantmentLevel; } return 0; } From d6cb5bd52db067f8b62725e7dea4f2e64b64c233 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 4 Apr 2022 14:08:35 -0400 Subject: [PATCH 076/110] ItemMappings: use array for ItemMapping class --- .../inventory/item/StoredItemMappings.java | 2 -- .../populator/ItemRegistryPopulator.java | 19 +++++++------ .../geyser/registry/type/ItemMappings.java | 28 ++++++++++++++----- .../inventory/item/CompassTranslator.java | 11 ++++---- .../inventory/item/PotionTranslator.java | 7 ++--- .../inventory/item/TippedArrowTranslator.java | 7 ++--- .../inventory/item/nbt/BannerTranslator.java | 11 ++------ ...BedrockInventoryTransactionTranslator.java | 2 +- .../java/JavaUpdateRecipesTranslator.java | 5 ++-- 9 files changed, 48 insertions(+), 44 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/inventory/item/StoredItemMappings.java b/core/src/main/java/org/geysermc/geyser/inventory/item/StoredItemMappings.java index e4296c2d4..c787f87a1 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/item/StoredItemMappings.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/item/StoredItemMappings.java @@ -52,7 +52,6 @@ public class StoredItemMappings { private final int goldIngot; private final int ironIngot; private final int lead; - private final ItemMapping lodestoneCompass; private final ItemMapping milkBucket; private final int nameTag; private final ItemMapping powderSnowBucket; @@ -80,7 +79,6 @@ public class StoredItemMappings { this.goldIngot = load(itemMappings, "gold_ingot").getJavaId(); this.ironIngot = load(itemMappings, "iron_ingot").getJavaId(); this.lead = load(itemMappings, "lead").getJavaId(); - this.lodestoneCompass = load(itemMappings, "lodestone_compass"); this.milkBucket = load(itemMappings, "milk_bucket"); this.nameTag = load(itemMappings, "name_tag").getJavaId(); this.powderSnowBucket = load(itemMappings, "powder_snow_bucket"); diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java index 534c68776..0e12669e3 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java @@ -128,7 +128,7 @@ public class ItemRegistryPopulator { IntList spawnEggs = new IntArrayList(); List carpets = new ObjectArrayList<>(); - Int2ObjectMap mappings = new Int2ObjectOpenHashMap<>(); + List mappings = new ObjectArrayList<>(); // Temporary mapping to create stored items Map identifierToMapping = new Object2ObjectOpenHashMap<>(); @@ -243,6 +243,8 @@ public class ItemRegistryPopulator { if (usingFurnaceMinecart && javaIdentifier.equals("minecraft:furnace_minecart")) { javaFurnaceMinecartId = itemIndex; itemIndex++; + // Will be added later + mappings.add(null); continue; } @@ -419,7 +421,7 @@ public class ItemRegistryPopulator { spawnEggs.add(mapping.getBedrockId()); } - mappings.put(itemIndex, mapping); + mappings.add(mapping); identifierToMapping.put(javaIdentifier, mapping); itemNames.add(javaIdentifier); @@ -440,16 +442,14 @@ public class ItemRegistryPopulator { // Add the lodestone compass since it doesn't exist on java but we need it for item conversion ItemMapping lodestoneEntry = ItemMapping.builder() - .javaIdentifier("minecraft:lodestone_compass") + .javaIdentifier("") .bedrockIdentifier("minecraft:lodestone_compass") - .javaId(itemIndex) + .javaId(-1) .bedrockId(lodestoneCompassId) .bedrockData(0) .bedrockBlockId(-1) .stackSize(1) .build(); - mappings.put(itemIndex, lodestoneEntry); - identifierToMapping.put(lodestoneEntry.getJavaIdentifier(), lodestoneEntry); ComponentItemData furnaceMinecartData = null; if (usingFurnaceMinecart) { @@ -458,7 +458,7 @@ public class ItemRegistryPopulator { entries.put("geysermc:furnace_minecart", new StartGamePacket.ItemEntry("geysermc:furnace_minecart", (short) furnaceMinecartId, true)); - mappings.put(javaFurnaceMinecartId, ItemMapping.builder() + mappings.set(javaFurnaceMinecartId, ItemMapping.builder() .javaIdentifier("minecraft:furnace_minecart") .bedrockIdentifier("geysermc:furnace_minecart") .javaId(javaFurnaceMinecartId) @@ -509,9 +509,9 @@ public class ItemRegistryPopulator { } ItemMappings itemMappings = ItemMappings.builder() - .items(mappings) + .items(mappings.toArray(new ItemMapping[0])) .creativeItems(creativeItems.toArray(new ItemData[0])) - .itemEntries(new ArrayList<>(entries.values())) + .itemEntries(List.copyOf(entries.values())) .itemNames(itemNames.toArray(new String[0])) .storedItems(new StoredItemMappings(identifierToMapping)) .javaOnlyItems(javaOnlyItems) @@ -520,6 +520,7 @@ public class ItemRegistryPopulator { .spawnEggIds(spawnEggs) .carpets(carpets) .furnaceMinecartData(furnaceMinecartData) + .lodestoneCompass(lodestoneEntry) .build(); Registries.ITEMS.register(palette.getValue().protocolVersion(), itemMappings); diff --git a/core/src/main/java/org/geysermc/geyser/registry/type/ItemMappings.java b/core/src/main/java/org/geysermc/geyser/registry/type/ItemMappings.java index a4953b05b..3072568f3 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/type/ItemMappings.java +++ b/core/src/main/java/org/geysermc/geyser/registry/type/ItemMappings.java @@ -29,13 +29,13 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; 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.IntList; import lombok.Builder; import lombok.Value; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.inventory.item.StoredItemMappings; +import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.List; import java.util.Map; @@ -48,7 +48,12 @@ public class ItemMappings { Map cachedJavaMappings = new WeakHashMap<>(); - Int2ObjectMap items; + ItemMapping[] items; + + /** + * A unique exception as this is an item in Bedrock, but not in Java. + */ + ItemMapping lodestoneCompass; ItemData[] creativeItems; List itemEntries; @@ -70,6 +75,7 @@ public class ItemMappings { * @param itemStack the itemstack * @return an item entry from the given java edition identifier */ + @Nonnull public ItemMapping getMapping(ItemStack itemStack) { return this.getMapping(itemStack.getId()); } @@ -81,8 +87,9 @@ public class ItemMappings { * @param javaId the id * @return an item entry from the given java edition identifier */ + @Nonnull public ItemMapping getMapping(int javaId) { - return this.items.get(javaId); + return javaId >= 0 && javaId < this.items.length ? this.items[javaId] : ItemMapping.AIR; } /** @@ -94,7 +101,7 @@ public class ItemMappings { */ public ItemMapping getMapping(String javaIdentifier) { return this.cachedJavaMappings.computeIfAbsent(javaIdentifier, key -> { - for (ItemMapping mapping : this.items.values()) { + for (ItemMapping mapping : this.items) { if (mapping.getJavaIdentifier().equals(key)) { return mapping; } @@ -110,11 +117,18 @@ public class ItemMappings { * @return an item entry from the given item data */ public ItemMapping getMapping(ItemData data) { + int id = data.getId(); + if (id == 0) { + return ItemMapping.AIR; + } else if (id == lodestoneCompass.getBedrockId()) { + return lodestoneCompass; + } + boolean isBlock = data.getBlockRuntimeId() != 0; boolean hasDamage = data.getDamage() != 0; - for (ItemMapping mapping : this.items.values()) { - if (mapping.getBedrockId() == data.getId()) { + for (ItemMapping mapping : this.items) { + if (mapping.getBedrockId() == id) { if (isBlock && !hasDamage) { // Pre-1.16.220 will not use block runtime IDs at all, so we shouldn't check either if (data.getBlockRuntimeId() != mapping.getBedrockBlockId()) { continue; @@ -135,7 +149,7 @@ public class ItemMappings { } // This will hide the message when the player clicks with an empty hand - if (data.getId() != 0 && data.getDamage() != 0) { + if (id != 0 && data.getDamage() != 0) { GeyserImpl.getInstance().getLogger().debug("Missing mapping for bedrock item " + data.getId() + ":" + data.getDamage()); } return ItemMapping.AIR; diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/CompassTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/CompassTranslator.java index b8ef85f81..4c2978082 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/CompassTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/CompassTranslator.java @@ -35,6 +35,7 @@ import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMappings; +import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; @@ -45,7 +46,7 @@ public class CompassTranslator extends ItemTranslator { protected ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping mapping, ItemMappings mappings) { if (isLodestoneCompass(itemStack.getNbt())) { // NBT will be translated in nbt/LodestoneCompassTranslator if applicable - return super.translateToBedrock(itemStack, mappings.getStoredItems().lodestoneCompass(), mappings); + return super.translateToBedrock(itemStack, mappings.getLodestoneCompass(), mappings); } return super.translateToBedrock(itemStack, mapping, mappings); } @@ -53,7 +54,7 @@ public class CompassTranslator extends ItemTranslator { @Override protected ItemMapping getItemMapping(int javaId, CompoundTag nbt, ItemMappings mappings) { if (isLodestoneCompass(nbt)) { - return mappings.getStoredItems().lodestoneCompass(); + return mappings.getLodestoneCompass(); } return super.getItemMapping(javaId, nbt, mappings); } @@ -78,10 +79,8 @@ public class CompassTranslator extends ItemTranslator { @Override public List getAppliedItems() { - return Registries.ITEMS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) - .getItems() - .values() - .stream() + return Arrays.stream(Registries.ITEMS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) + .getItems()) .filter(entry -> entry.getJavaIdentifier().endsWith("compass")) .collect(Collectors.toList()); } diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/PotionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/PotionTranslator.java index 04183e095..bf16af38f 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/PotionTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/PotionTranslator.java @@ -36,6 +36,7 @@ import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMappings; +import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; @@ -73,10 +74,8 @@ public class PotionTranslator extends ItemTranslator { @Override public List getAppliedItems() { - return Registries.ITEMS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) - .getItems() - .values() - .stream() + return Arrays.stream(Registries.ITEMS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) + .getItems()) .filter(entry -> entry.getJavaIdentifier().endsWith("potion")) .collect(Collectors.toList()); } diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/TippedArrowTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/TippedArrowTranslator.java index b3aecb668..d831ce586 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/TippedArrowTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/TippedArrowTranslator.java @@ -36,6 +36,7 @@ import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMappings; +import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; @@ -80,10 +81,8 @@ public class TippedArrowTranslator extends ItemTranslator { @Override public List getAppliedItems() { - return Registries.ITEMS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) - .getItems() - .values() - .stream() + return Arrays.stream(Registries.ITEMS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) + .getItems()) .filter(entry -> entry.getJavaIdentifier().contains("arrow") && !entry.getJavaIdentifier().contains("spectral")) .collect(Collectors.toList()); diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/BannerTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/BannerTranslator.java index 3da157cfc..ed4865411 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/BannerTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/BannerTranslator.java @@ -37,10 +37,7 @@ import org.geysermc.geyser.translator.inventory.item.ItemRemapper; import org.geysermc.geyser.translator.inventory.item.NbtItemStackTranslator; import javax.annotation.Nonnull; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.stream.Collectors; @ItemRemapper @@ -79,10 +76,8 @@ public class BannerTranslator extends NbtItemStackTranslator { } public BannerTranslator() { - appliedItems = Registries.ITEMS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) - .getItems() - .values() - .stream() + appliedItems = Arrays.stream(Registries.ITEMS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) + .getItems()) .filter(entry -> entry.getJavaIdentifier().endsWith("banner")) .collect(Collectors.toList()); } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java index f120e4a19..073917293 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java @@ -284,7 +284,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator - session.getItemMappings().getItems() - .getOrDefault(stoneCuttingRecipeData.getResult().getId(), ItemMapping.AIR) + session.getItemMappings().getMapping(stoneCuttingRecipeData.getResult()) .getJavaIdentifier()))); // Now that it's sorted, let's translate these recipes @@ -229,7 +228,7 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator Date: Mon, 4 Apr 2022 21:03:43 -0400 Subject: [PATCH 077/110] Add recent Paper check and new vanilla check for block placement Fixes #2917 --- ...BedrockInventoryTransactionTranslator.java | 43 +++++++++++++++---- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java index 073917293..a5787c1c6 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java @@ -139,7 +139,8 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator { - Vector3i blockPos = BlockUtils.getBlockPosition(packet.getBlockPosition(), packet.getBlockFace()); + final Vector3i packetBlockPosition = packet.getBlockPosition(); + Vector3i blockPos = BlockUtils.getBlockPosition(packetBlockPosition, packet.getBlockFace()); if (session.getGeyser().getConfig().isDisableBedrockScaffolding()) { float yaw = session.getPlayerEntity().getYaw(); @@ -159,8 +160,8 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator - (session.getGameMode().equals(GameMode.CREATIVE) ? CREATIVE_EYE_HEIGHT_PLACE_DISTANCE : SURVIVAL_EYE_HEIGHT_PLACE_DISTANCE)) { + (creative ? CREATIVE_EYE_HEIGHT_PLACE_DISTANCE : SURVIVAL_EYE_HEIGHT_PLACE_DISTANCE)) { restoreCorrectBlock(session, blockPos, packet); return; } + double clickPositionFullX = (double) packetBlockPosition.getX() + (double) packet.getClickPosition().getX(); + double clickPositionFullY = (double) packetBlockPosition.getY() + (double) packet.getClickPosition().getY(); + double clickPositionFullZ = (double) packetBlockPosition.getZ() + (double) packet.getClickPosition().getZ(); + + // More recent Paper check - https://github.com/PaperMC/Paper/blob/87e11bf7fdf48ecdf3e1cae383c368b9b61d7df9/patches/server/0470-Move-range-check-for-block-placing-up.patch + double clickDiffX = playerPosition.getX() - clickPositionFullX; + double clickDiffY = playerPosition.getY() - clickPositionFullY; + double clickDiffZ = playerPosition.getZ() - clickPositionFullZ; + if (((clickDiffX * clickDiffX) + (clickDiffY * clickDiffY) + (clickDiffZ * clickDiffZ)) > + (creative ? CREATIVE_EYE_HEIGHT_PLACE_DISTANCE : SURVIVAL_EYE_HEIGHT_PLACE_DISTANCE)) { + restoreCorrectBlock(session, blockPos, packet); + return; + } + + Vector3f blockCenter = Vector3f.from(packetBlockPosition.getX() + 0.5f, packetBlockPosition.getY() + 0.5f, packetBlockPosition.getZ() + 0.5f); // Vanilla check if (!(session.getPlayerEntity().getPosition().sub(0, EntityDefinitions.PLAYER.offset(), 0) - .distanceSquared(packet.getBlockPosition().toFloat().add(0.5f, 0.5f, 0.5f)) < MAXIMUM_BLOCK_PLACING_DISTANCE)) { + .distanceSquared(blockCenter) < MAXIMUM_BLOCK_PLACING_DISTANCE)) { // The client thinks that its blocks have been successfully placed. Restore the server's blocks instead. restoreCorrectBlock(session, blockPos, packet); return; } + + // More recent vanilla check (as of 1.18.2) + double clickDistanceX = clickPositionFullX - blockCenter.getX(); + double clickDistanceY = clickPositionFullY - blockCenter.getY(); + double clickDistanceZ = clickPositionFullZ - blockCenter.getZ(); + if (!(Math.abs(clickDistanceX) < 1.0000001D && Math.abs(clickDistanceY) < 1.0000001D && Math.abs(clickDistanceZ) < 1.0000001D)) { + restoreCorrectBlock(session, blockPos, packet); + return; + } /* Block place checks end - client is good to go */ From cb8858fc423bdba64b8d6ee2b08b7870261e9383 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 11 Apr 2022 15:44:15 -0400 Subject: [PATCH 078/110] Don't always store cert/client data used for skin uploaded This takes up a decent 30K of memory that we don't use after the skin is uploaded. The GameProfileTranslator cannot be run more than once per session. --- .../org/geysermc/geyser/session/GeyserSession.java | 6 ++++++ .../org/geysermc/geyser/session/auth/AuthData.java | 13 +------------ .../geyser/session/auth/BedrockClientData.java | 6 ++++++ .../protocol/java/JavaGameProfileTranslator.java | 8 +++++++- .../geysermc/geyser/util/LoginEncryptionUtils.java | 6 ++++-- 5 files changed, 24 insertions(+), 15 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index 1c25c2281..aeb8e9970 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -25,6 +25,7 @@ package org.geysermc.geyser.session; +import com.fasterxml.jackson.databind.JsonNode; import com.github.steveice10.mc.auth.data.GameProfile; import com.github.steveice10.mc.auth.exception.request.InvalidCredentialsException; import com.github.steveice10.mc.auth.exception.request.RequestException; @@ -142,6 +143,11 @@ public class GeyserSession implements GeyserConnection, CommandSender { private AuthData authData; @Setter private BedrockClientData clientData; + /** + * Used for Floodgate skin uploading + */ + @Setter + private JsonNode certChainData; /* Setter for GeyserConnect */ @Setter diff --git a/core/src/main/java/org/geysermc/geyser/session/auth/AuthData.java b/core/src/main/java/org/geysermc/geyser/session/auth/AuthData.java index 802ee3ca0..99b7ae3af 100644 --- a/core/src/main/java/org/geysermc/geyser/session/auth/AuthData.java +++ b/core/src/main/java/org/geysermc/geyser/session/auth/AuthData.java @@ -25,18 +25,7 @@ package org.geysermc.geyser.session.auth; -import com.fasterxml.jackson.databind.JsonNode; -import org.geysermc.geyser.GeyserImpl; - import java.util.UUID; -public record AuthData(String name, UUID uuid, String xuid, - JsonNode certChainData, String clientData) { - - public void upload(GeyserImpl geyser) { - // we can't upload the skin in LoginEncryptionUtil since the global server would return - // the skin too fast, that's why we upload it after we know for sure that the target server - // is ready to handle the result of the global server - geyser.getSkinUploader().uploadSkin(certChainData, clientData); - } +public record AuthData(String name, UUID uuid, String xuid) { } diff --git a/core/src/main/java/org/geysermc/geyser/session/auth/BedrockClientData.java b/core/src/main/java/org/geysermc/geyser/session/auth/BedrockClientData.java index b3601f6c3..07dd38491 100644 --- a/core/src/main/java/org/geysermc/geyser/session/auth/BedrockClientData.java +++ b/core/src/main/java/org/geysermc/geyser/session/auth/BedrockClientData.java @@ -25,9 +25,11 @@ package org.geysermc.geyser.session.auth; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; +import lombok.Setter; import org.geysermc.floodgate.util.DeviceOs; import org.geysermc.floodgate.util.InputMode; import org.geysermc.floodgate.util.UiProfile; @@ -107,6 +109,10 @@ public final class BedrockClientData { @JsonProperty(value = "PlayFabId") private String playFabId; + @JsonIgnore + @Setter + private String originalString = null; + public DeviceOs getDeviceOs() { return deviceOs != null ? deviceOs : DeviceOs.UNKNOWN; } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaGameProfileTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaGameProfileTranslator.java index c35261e78..199d29e30 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaGameProfileTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaGameProfileTranslator.java @@ -57,7 +57,13 @@ public class JavaGameProfileTranslator extends PacketTranslator Date: Tue, 12 Apr 2022 19:04:09 -0400 Subject: [PATCH 079/110] Make all moon phases visible The fix to prevent integer overflows also prevented moon phases from being visible until now. Fixes #2927 --- .../protocol/java/level/JavaSetTimeTranslator.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSetTimeTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSetTimeTranslator.java index 581433a5f..781ae4ec7 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSetTimeTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSetTimeTranslator.java @@ -42,7 +42,10 @@ public class JavaSetTimeTranslator extends PacketTranslator= 0) { // Client thinks there is no daylight cycle but there is From 0803c5d9af291224fcf3f8a3cc7f458b2c2cc957 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 12 Apr 2022 19:42:41 -0400 Subject: [PATCH 080/110] SetTimeTranslator: cast from long on the entire modulus This should fix some inaccuracies with time on older worlds. --- .../translator/protocol/java/level/JavaSetTimeTranslator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSetTimeTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSetTimeTranslator.java index 781ae4ec7..bc4e8c1ff 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSetTimeTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSetTimeTranslator.java @@ -45,7 +45,7 @@ public class JavaSetTimeTranslator extends PacketTranslator= 0) { // Client thinks there is no daylight cycle but there is From cf8114543e38032e02f27d672f8f98c919e2c112 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 17 Apr 2022 19:53:06 -0400 Subject: [PATCH 081/110] Bump version; drop 1.17.40; support 1.18.30 --- README.md | 2 +- core/pom.xml | 4 +- .../entity/type/player/PlayerEntity.java | 2 + .../entity/type/player/SkullPlayerEntity.java | 2 + .../geyser/level/BedrockDimension.java | 41 + .../level/block/BlockPositionIterator.java | 2 - .../geyser/network/MinecraftProtocol.java | 13 +- .../geyser/network/UpstreamPacketHandler.java | 15 +- .../populator/BlockRegistryPopulator.java | 72 +- .../populator/ItemRegistryPopulator.java | 30 +- .../geyser/session/GeyserSession.java | 7 +- .../geyser/session/cache/ChunkCache.java | 5 +- .../item/nbt/EnchantmentTranslator.java | 2 +- .../player/BedrockMovePlayerTranslator.java | 13 +- .../JavaLevelChunkWithLightTranslator.java | 39 +- .../org/geysermc/geyser/util/ChunkUtils.java | 29 +- .../geysermc/geyser/util/DimensionUtils.java | 2 +- ...e.1_17_40.nbt => block_palette.1_18_0.nbt} | Bin .../bedrock/block_palette.1_18_30.nbt | Bin 0 -> 45536 bytes ...17_40.json => creative_items.1_18_30.json} | 2529 +++++++++-------- ....json => runtime_item_states.1_18_30.json} | 264 +- 21 files changed, 1614 insertions(+), 1459 deletions(-) create mode 100644 core/src/main/java/org/geysermc/geyser/level/BedrockDimension.java rename core/src/main/resources/bedrock/{block_palette.1_17_40.nbt => block_palette.1_18_0.nbt} (100%) create mode 100644 core/src/main/resources/bedrock/block_palette.1_18_30.nbt rename core/src/main/resources/bedrock/{creative_items.1_17_40.json => creative_items.1_18_30.json} (84%) rename core/src/main/resources/bedrock/{runtime_item_states.1_17_40.json => runtime_item_states.1_18_30.json} (96%) diff --git a/README.md b/README.md index 23bde93d2..bbb9532a5 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have joined us here! -### Currently supporting Minecraft Bedrock 1.17.41 + 1.18.0 - 1.18.10 and Minecraft Java 1.18.2. +### Currently supporting Minecraft Bedrock 1.18.0 - 1.18.30 and Minecraft Java 1.18.2. ## Setting Up Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Geyser. diff --git a/core/pom.xml b/core/pom.xml index d40b7dcc4..7dc64e08b 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -120,8 +120,8 @@ com.github.CloudburstMC.Protocol - bedrock-v486 - 0cd24c0 + bedrock-v503 + 29ecd7a compile diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java index 58f04a756..0d6c0dac1 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java @@ -37,6 +37,7 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.AttributeData; +import com.nukkitx.protocol.bedrock.data.GameType; import com.nukkitx.protocol.bedrock.data.PlayerPermission; import com.nukkitx.protocol.bedrock.data.command.CommandPermission; import com.nukkitx.protocol.bedrock.data.entity.EntityData; @@ -126,6 +127,7 @@ public class PlayerEntity extends LivingEntity { addPlayerPacket.getAdventureSettings().setPlayerPermission(PlayerPermission.MEMBER); addPlayerPacket.setDeviceId(""); addPlayerPacket.setPlatformChatId(""); + addPlayerPacket.setGameType(GameType.SURVIVAL); //TODO addPlayerPacket.getMetadata().putFlags(flags); dirtyMetadata.apply(addPlayerPacket.getMetadata()); diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/SkullPlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/SkullPlayerEntity.java index ce1615816..f1a447b57 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/SkullPlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/SkullPlayerEntity.java @@ -27,6 +27,7 @@ package org.geysermc.geyser.entity.type.player; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.protocol.bedrock.data.GameType; import com.nukkitx.protocol.bedrock.data.PlayerPermission; import com.nukkitx.protocol.bedrock.data.command.CommandPermission; import com.nukkitx.protocol.bedrock.data.entity.EntityData; @@ -84,6 +85,7 @@ public class SkullPlayerEntity extends PlayerEntity { addPlayerPacket.getAdventureSettings().setPlayerPermission(PlayerPermission.MEMBER); addPlayerPacket.setDeviceId(""); addPlayerPacket.setPlatformChatId(""); + addPlayerPacket.setGameType(GameType.SURVIVAL); addPlayerPacket.getMetadata().putFlags(flags); dirtyMetadata.apply(addPlayerPacket.getMetadata()); diff --git a/core/src/main/java/org/geysermc/geyser/level/BedrockDimension.java b/core/src/main/java/org/geysermc/geyser/level/BedrockDimension.java new file mode 100644 index 000000000..78c6b2c6a --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/level/BedrockDimension.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019-2022 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.level; + +/** + * A data structure to represent what Bedrock believes are the height requirements for a specific dimension. + * As of 1.18.30, biome count is representative of the height of the world, and out-of-bounds chunks can crash + * the client. + * + * @param minY The minimum height Bedrock Edition will accept. + * @param height The maximum chunk height Bedrock Edition will accept, from the lowest point to the highest. + * @param doUpperHeightWarn whether to warn in the console if the Java dimension height exceeds Bedrock's. + */ +public record BedrockDimension(int minY, int height, boolean doUpperHeightWarn) { + public static BedrockDimension OVERWORLD = new BedrockDimension(-64, 384, true); + public static BedrockDimension THE_NETHER = new BedrockDimension(0, 128, false); + public static BedrockDimension THE_END = new BedrockDimension(0, 256, true); +} diff --git a/core/src/main/java/org/geysermc/geyser/level/block/BlockPositionIterator.java b/core/src/main/java/org/geysermc/geyser/level/block/BlockPositionIterator.java index 425c78f18..d22150ccf 100644 --- a/core/src/main/java/org/geysermc/geyser/level/block/BlockPositionIterator.java +++ b/core/src/main/java/org/geysermc/geyser/level/block/BlockPositionIterator.java @@ -26,8 +26,6 @@ package org.geysermc.geyser.level.block; import com.nukkitx.network.util.Preconditions; -import lombok.Getter; - public class BlockPositionIterator { private final int minX; diff --git a/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java b/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java index 7ab381375..0f5782f86 100644 --- a/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java +++ b/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java @@ -28,11 +28,14 @@ package org.geysermc.geyser.network; import com.github.steveice10.mc.protocol.codec.MinecraftCodec; import com.github.steveice10.mc.protocol.codec.PacketCodec; import com.nukkitx.protocol.bedrock.BedrockPacketCodec; -import com.nukkitx.protocol.bedrock.v471.Bedrock_v471; import com.nukkitx.protocol.bedrock.v475.Bedrock_v475; import com.nukkitx.protocol.bedrock.v486.Bedrock_v486; +import com.nukkitx.protocol.bedrock.v503.Bedrock_v503; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.StringJoiner; /** * Contains information about the supported protocols in Geyser. @@ -42,7 +45,7 @@ public final class MinecraftProtocol { * Default Bedrock codec that should act as a fallback. Should represent the latest available * release of the game that Geyser supports. */ - public static final BedrockPacketCodec DEFAULT_BEDROCK_CODEC = Bedrock_v486.V486_CODEC; + public static final BedrockPacketCodec DEFAULT_BEDROCK_CODEC = Bedrock_v503.V503_CODEC; /** * A list of all supported Bedrock versions that can join Geyser */ @@ -55,11 +58,11 @@ public final class MinecraftProtocol { private static final PacketCodec DEFAULT_JAVA_CODEC = MinecraftCodec.CODEC; static { - SUPPORTED_BEDROCK_CODECS.add(Bedrock_v471.V471_CODEC); SUPPORTED_BEDROCK_CODECS.add(Bedrock_v475.V475_CODEC.toBuilder().minecraftVersion("1.18.0/1.18.1/1.18.2").build()); - SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC.toBuilder() + SUPPORTED_BEDROCK_CODECS.add(Bedrock_v486.V486_CODEC.toBuilder() .minecraftVersion("1.18.10/1.18.12") // 1.18.11 is also supported, but was only on Switch and since that auto-updates it's not needed .build()); + SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC); } /** diff --git a/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java index 5ded35259..5ae6fbca9 100644 --- a/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java @@ -30,18 +30,18 @@ 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.v471.Bedrock_v471; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.session.PendingMicrosoftAuthentication; -import org.geysermc.geyser.session.auth.AuthType; import org.geysermc.geyser.configuration.GeyserConfiguration; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.pack.ResourcePack; import org.geysermc.geyser.pack.ResourcePackManifest; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.Registries; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.session.PendingMicrosoftAuthentication; +import org.geysermc.geyser.session.auth.AuthType; import org.geysermc.geyser.text.GeyserLocale; -import org.geysermc.geyser.util.*; +import org.geysermc.geyser.util.LoginEncryptionUtils; +import org.geysermc.geyser.util.MathUtils; import java.io.FileInputStream; import java.io.InputStream; @@ -165,11 +165,6 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { stackPacket.getExperiments().add(new ExperimentData("data_driven_items", true)); } - if (session.getUpstream().getProtocolVersion() <= Bedrock_v471.V471_CODEC.getProtocolVersion()) { - // Allow extended world height in the overworld to work for pre-1.18 clients - stackPacket.getExperiments().add(new ExperimentData("caves_and_cliffs", true)); - } - session.sendUpstreamPacket(stackPacket); break; diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java index 8238bcea1..d8aa6a456 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java @@ -28,8 +28,9 @@ package org.geysermc.geyser.registry.populator; import com.fasterxml.jackson.databind.JsonNode; import com.google.common.collect.ImmutableMap; import com.nukkitx.nbt.*; -import com.nukkitx.protocol.bedrock.v471.Bedrock_v471; +import com.nukkitx.protocol.bedrock.v475.Bedrock_v475; import com.nukkitx.protocol.bedrock.v486.Bedrock_v486; +import com.nukkitx.protocol.bedrock.v503.Bedrock_v503; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.objects.Object2IntMap; @@ -60,35 +61,50 @@ public class BlockRegistryPopulator { private static final ImmutableMap, BiFunction> BLOCK_MAPPERS; private static final BiFunction EMPTY_MAPPER = (bedrockIdentifier, statesBuilder) -> null; + private static final BiFunction V486_MAPPER = (bedrockIdentifier, statesBuilder) -> { + statesBuilder.remove("no_drop_bit"); // Used in skulls + if (bedrockIdentifier.equals("minecraft:glow_lichen")) { + // Moved around north, south, west + int bits = (int) statesBuilder.get("multi_face_direction_bits"); + boolean north = (bits & (1 << 2)) != 0; + boolean south = (bits & (1 << 3)) != 0; + boolean west = (bits & (1 << 4)) != 0; + if (north) { + bits |= 1 << 4; + } else { + bits &= ~(1 << 4); + } + if (south) { + bits |= 1 << 2; + } else { + bits &= ~(1 << 2); + } + if (west) { + bits |= 1 << 3; + } else { + bits &= ~(1 << 3); + } + statesBuilder.put("multi_face_direction_bits", bits); + } + return null; + }; + static { ImmutableMap.Builder, BiFunction> stateMapperBuilder = ImmutableMap., BiFunction>builder() - .put(ObjectIntPair.of("1_17_40", Bedrock_v471.V471_CODEC.getProtocolVersion()), EMPTY_MAPPER) - .put(ObjectIntPair.of("1_18_10", Bedrock_v486.V486_CODEC.getProtocolVersion()), (bedrockIdentifier, statesBuilder) -> { - statesBuilder.remove("no_drop_bit"); // Used in skulls - if (bedrockIdentifier.equals("minecraft:glow_lichen")) { - // Moved around north, south, west - int bits = (int) statesBuilder.get("multi_face_direction_bits"); - boolean north = (bits & (1 << 2)) != 0; - boolean south = (bits & (1 << 3)) != 0; - boolean west = (bits & (1 << 4)) != 0; - if (north) { - bits |= 1 << 4; - } else { - bits &= ~(1 << 4); - } - if (south) { - bits |= 1 << 2; - } else { - bits &= ~(1 << 2); - } - if (west) { - bits |= 1 << 3; - } else { - bits &= ~(1 << 3); - } - statesBuilder.put("multi_face_direction_bits", bits); - } - return null; + .put(ObjectIntPair.of("1_18_0", Bedrock_v475.V475_CODEC.getProtocolVersion()), EMPTY_MAPPER) + .put(ObjectIntPair.of("1_18_10", Bedrock_v486.V486_CODEC.getProtocolVersion()), V486_MAPPER) + .put(ObjectIntPair.of("1_18_30", Bedrock_v503.V503_CODEC.getProtocolVersion()), (bedrockIdentifier, statesBuilder) -> { + // Apply these fixes too + V486_MAPPER.apply(bedrockIdentifier, statesBuilder); + return switch (bedrockIdentifier) { + case "minecraft:pistonArmCollision" -> "minecraft:piston_arm_collision"; + case "minecraft:stickyPistonArmCollision" -> "minecraft:sticky_piston_arm_collision"; + case "minecraft:movingBlock" -> "minecraft:moving_block"; + case "minecraft:tripWire" -> "minecraft:trip_wire"; + case "minecraft:seaLantern" -> "minecraft:sea_lantern"; + case "minecraft:concretePowder" -> "minecraft:concrete_powder"; + default -> null; + }; }); BLOCK_MAPPERS = stateMapperBuilder.build(); diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java index 0e12669e3..37b6c49f4 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java @@ -35,10 +35,12 @@ import com.nukkitx.protocol.bedrock.data.SoundEvent; import com.nukkitx.protocol.bedrock.data.inventory.ComponentItemData; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.packet.StartGamePacket; -import com.nukkitx.protocol.bedrock.v471.Bedrock_v471; import com.nukkitx.protocol.bedrock.v475.Bedrock_v475; import com.nukkitx.protocol.bedrock.v486.Bedrock_v486; -import it.unimi.dsi.fastutil.ints.*; +import com.nukkitx.protocol.bedrock.v503.Bedrock_v503; +import it.unimi.dsi.fastutil.ints.Int2IntMap; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.objects.*; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; @@ -58,19 +60,16 @@ import java.util.*; * Populates the item registries. */ public class ItemRegistryPopulator { - private static final Map PALETTE_VERSIONS; - - static { - PALETTE_VERSIONS = new Object2ObjectOpenHashMap<>(); - PALETTE_VERSIONS.put("1_17_40", new PaletteVersion(Bedrock_v471.V471_CODEC.getProtocolVersion(), Collections.emptyMap())); - PALETTE_VERSIONS.put("1_18_0", new PaletteVersion(Bedrock_v475.V475_CODEC.getProtocolVersion(), Collections.emptyMap())); - PALETTE_VERSIONS.put("1_18_10", new PaletteVersion(Bedrock_v486.V486_CODEC.getProtocolVersion(), Collections.emptyMap())); - } private record PaletteVersion(int protocolVersion, Map additionalTranslatedItems) { } public static void populate() { + Map paletteVersions = new Object2ObjectOpenHashMap<>(); + paletteVersions.put("1_18_0", new PaletteVersion(Bedrock_v475.V475_CODEC.getProtocolVersion(), Collections.emptyMap())); + paletteVersions.put("1_18_10", new PaletteVersion(Bedrock_v486.V486_CODEC.getProtocolVersion(), Collections.emptyMap())); + paletteVersions.put("1_18_30", new PaletteVersion(Bedrock_v503.V503_CODEC.getProtocolVersion(), Collections.emptyMap())); + GeyserBootstrap bootstrap = GeyserImpl.getInstance().getBootstrap(); TypeReference> mappingItemsType = new TypeReference<>() { }; @@ -88,7 +87,7 @@ public class ItemRegistryPopulator { Int2IntMap dyeColors = new FixedInt2IntMap(); /* Load item palette */ - for (Map.Entry palette : PALETTE_VERSIONS.entrySet()) { + for (Map.Entry palette : paletteVersions.entrySet()) { TypeReference> paletteEntriesType = new TypeReference<>() {}; // Used to get the Bedrock namespaced ID (in instances where there are small differences) @@ -232,12 +231,15 @@ public class ItemRegistryPopulator { } String bedrockIdentifier; - if (javaIdentifier.equals("minecraft:music_disc_otherside") && palette.getValue().protocolVersion() <= Bedrock_v471.V471_CODEC.getProtocolVersion()) { - bedrockIdentifier = "minecraft:music_disc_pigstep"; - } else if (javaIdentifier.equals("minecraft:globe_banner_pattern") && palette.getValue().protocolVersion() < Bedrock_v486.V486_CODEC.getProtocolVersion()) { + if (javaIdentifier.equals("minecraft:globe_banner_pattern") && palette.getValue().protocolVersion() < Bedrock_v486.V486_CODEC.getProtocolVersion()) { bedrockIdentifier = "minecraft:banner_pattern"; } else { bedrockIdentifier = mappingItem.getBedrockIdentifier(); + if (palette.getValue().protocolVersion() >= Bedrock_v503.V503_CODEC.getProtocolVersion()) { + if (bedrockIdentifier.equals("minecraft:sealantern")) { + bedrockIdentifier = "minecraft:sea_lantern"; + } + } } if (usingFurnaceMinecart && javaIdentifier.equals("minecraft:furnace_minecart")) { diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index aeb8e9970..f35378af3 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -67,7 +67,6 @@ import com.nukkitx.protocol.bedrock.data.*; import com.nukkitx.protocol.bedrock.data.command.CommandPermission; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.packet.*; -import com.nukkitx.protocol.bedrock.v471.Bedrock_v471; import io.netty.channel.Channel; import io.netty.channel.EventLoop; import it.unimi.dsi.fastutil.ints.*; @@ -1388,7 +1387,7 @@ public class GeyserSession implements GeyserConnection, CommandSender { startGamePacket.setPlayerPosition(Vector3f.from(0, 69, 0)); startGamePacket.setRotation(Vector2f.from(1, 1)); - startGamePacket.setSeed(-1); + startGamePacket.setSeed(-1L); startGamePacket.setDimensionId(DimensionUtils.javaToBedrock(dimension)); startGamePacket.setGeneratorId(1); startGamePacket.setLevelGameType(GameType.SURVIVAL); @@ -1437,10 +1436,6 @@ public class GeyserSession implements GeyserConnection, CommandSender { settings.setServerAuthoritativeBlockBreaking(false); startGamePacket.setPlayerMovementSettings(settings); - if (upstream.getProtocolVersion() <= Bedrock_v471.V471_CODEC.getProtocolVersion()) { - startGamePacket.getExperiments().add(new ExperimentData("caves_and_cliffs", true)); - } - upstream.sendPacket(startGamePacket); } diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/ChunkCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/ChunkCache.java index feb1cf3a8..91d6b33d6 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/ChunkCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/ChunkCache.java @@ -33,6 +33,7 @@ import lombok.Setter; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.level.chunk.GeyserChunk; +import org.geysermc.geyser.level.BedrockDimension; import org.geysermc.geyser.util.MathUtils; public class ChunkCache { @@ -45,11 +46,11 @@ public class ChunkCache { private int heightY; /** - * Whether the Bedrock client believes they are in a world with a minimum of -64 and maximum of 320 + * Which dimension Bedrock understands themselves to be in. */ @Getter @Setter - private boolean isExtendedHeight = false; + private BedrockDimension bedrockDimension = BedrockDimension.OVERWORLD; public ChunkCache(GeyserSession session) { this.cache = !session.getGeyser().getWorldManager().hasOwnChunkCache(); // To prevent Spigot from initializing diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/EnchantmentTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/EnchantmentTranslator.java index 847a70a27..55d45f67e 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/EnchantmentTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/EnchantmentTranslator.java @@ -136,7 +136,7 @@ public class EnchantmentTranslator extends NbtItemStackTranslator { CompoundTag bedrockTag = new CompoundTag(""); bedrockTag.put(new ShortTag("id", (short) enchantment.ordinal())); // If the tag cannot parse, Java Edition 1.18.2 sets to 0 - bedrockTag.put(new ShortTag("lvl", javaEnchLvl != null && javaEnchLvl.getValue() instanceof Number lvl ? lvl.shortValue() : 0)); + bedrockTag.put(new ShortTag("lvl", javaEnchLvl != null && javaEnchLvl.getValue() instanceof Number lvl ? lvl.shortValue() : (short) 0)); return bedrockTag; } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockMovePlayerTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockMovePlayerTranslator.java index 2fccbe482..a63c0f334 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockMovePlayerTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockMovePlayerTranslator.java @@ -37,14 +37,12 @@ import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.ChatColor; +import org.geysermc.geyser.level.BedrockDimension; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; @Translator(packet = MovePlayerPacket.class) public class BedrockMovePlayerTranslator extends PacketTranslator { - /* The upper and lower bounds to check for the void floor that only exists in Bedrock. These are the constants for the overworld. */ - private static final int BEDROCK_OVERWORLD_VOID_FLOOR_UPPER_Y = -104; - private static final int BEDROCK_OVERWORLD_VOID_FLOOR_LOWER_Y = BEDROCK_OVERWORLD_VOID_FLOOR_UPPER_Y + 2; @Override public void translate(GeyserSession session, MovePlayerPacket packet) { @@ -124,11 +122,10 @@ public class BedrockMovePlayerTranslator extends PacketTranslator= (extendedWorld ? BEDROCK_OVERWORLD_VOID_FLOOR_UPPER_Y : -40)) { + // The void floor is offset about 40 blocks below the bottom of the world + BedrockDimension bedrockDimension = session.getChunkCache().getBedrockDimension(); + int voidFloorLocation = bedrockDimension.minY() - 40; + if (floorY <= (voidFloorLocation + 2) && floorY >= voidFloorLocation) { // Work around there being a floor at the bottom of the world and teleport the player below it // Moving from below to above the void floor works fine entity.setPosition(entity.getPosition().sub(0, 4f, 0)); diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelChunkWithLightTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelChunkWithLightTranslator.java index 97b826473..165d90b36 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelChunkWithLightTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelChunkWithLightTranslator.java @@ -43,7 +43,7 @@ import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtUtils; import com.nukkitx.network.VarInts; import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket; -import com.nukkitx.protocol.bedrock.v475.Bedrock_v475; +import com.nukkitx.protocol.bedrock.v503.Bedrock_v503; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.ByteBufOutputStream; @@ -52,26 +52,29 @@ import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.ints.IntLists; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import org.geysermc.geyser.entity.type.ItemFrameEntity; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.translator.protocol.PacketTranslator; -import org.geysermc.geyser.translator.protocol.Translator; -import org.geysermc.geyser.translator.level.BiomeTranslator; import org.geysermc.geyser.level.block.BlockStateValues; -import org.geysermc.geyser.translator.level.block.entity.BedrockOnlyBlockEntity; -import org.geysermc.geyser.translator.level.block.entity.BlockEntityTranslator; -import org.geysermc.geyser.translator.level.block.entity.SkullBlockEntityTranslator; import org.geysermc.geyser.level.chunk.BlockStorage; import org.geysermc.geyser.level.chunk.GeyserChunkSection; import org.geysermc.geyser.level.chunk.bitarray.BitArray; import org.geysermc.geyser.level.chunk.bitarray.BitArrayVersion; import org.geysermc.geyser.level.chunk.bitarray.SingletonBitArray; import org.geysermc.geyser.registry.BlockRegistries; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.level.BedrockDimension; +import org.geysermc.geyser.translator.level.BiomeTranslator; +import org.geysermc.geyser.translator.level.block.entity.BedrockOnlyBlockEntity; +import org.geysermc.geyser.translator.level.block.entity.BlockEntityTranslator; +import org.geysermc.geyser.translator.level.block.entity.SkullBlockEntityTranslator; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; import org.geysermc.geyser.util.BlockEntityUtils; import org.geysermc.geyser.util.ChunkUtils; import java.io.ByteArrayInputStream; import java.io.IOException; -import java.util.*; +import java.util.BitSet; +import java.util.List; +import java.util.Map; import static org.geysermc.geyser.util.ChunkUtils.*; @@ -98,13 +101,13 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator> 4) - 1; + BedrockDimension bedrockDimension = session.getChunkCache().getBedrockDimension(); + int maxBedrockSectionY = (bedrockDimension.height() >> 4) - 1; int sectionCount; byte[] payload; ByteBuf byteBuf = null; - GeyserChunkSection[] sections = new GeyserChunkSection[javaChunks.length - (yOffset + ((overworld ? MINIMUM_ACCEPTED_HEIGHT_OVERWORLD : MINIMUM_ACCEPTED_HEIGHT) >> 4))]; + GeyserChunkSection[] sections = new GeyserChunkSection[javaChunks.length - (yOffset + (bedrockDimension.minY() >> 4))]; try { NetInput in = new StreamNetInput(new ByteArrayInputStream(packet.getChunkData())); @@ -113,7 +116,7 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator> 4)); + int bedrockSectionY = sectionY + (yOffset - (bedrockDimension.minY() >> 4)); if (bedrockSectionY < 0 || maxBedrockSectionY < bedrockSectionY) { // Ignore this chunk section since it goes outside the bounds accepted by the Bedrock client continue; @@ -309,11 +312,11 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator= Bedrock_v475.V475_CODEC.getProtocolVersion(); - int biomeCount = isNewVersion ? 25 : 32; - int dimensionOffset = (overworld ? MINIMUM_ACCEPTED_HEIGHT_OVERWORLD : MINIMUM_ACCEPTED_HEIGHT) >> 4; + // As of 1.18.0, Bedrock hardcodes to always read 25 biome sections + // As of 1.18.30, the hardcode may now be tied to the dimension definition + boolean isNewVersion = session.getUpstream().getProtocolVersion() >= Bedrock_v503.V503_CODEC.getProtocolVersion(); + int biomeCount = isNewVersion ? bedrockDimension.height() >> 4 : 25; + int dimensionOffset = bedrockDimension.minY() >> 4; for (int i = 0; i < biomeCount; i++) { int biomeYOffset = dimensionOffset + i; if (biomeYOffset < yOffset) { diff --git a/core/src/main/java/org/geysermc/geyser/util/ChunkUtils.java b/core/src/main/java/org/geysermc/geyser/util/ChunkUtils.java index 7fdf12ec9..445ffb882 100644 --- a/core/src/main/java/org/geysermc/geyser/util/ChunkUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/ChunkUtils.java @@ -46,23 +46,13 @@ import org.geysermc.geyser.level.chunk.bitarray.SingletonBitArray; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.GeyserLocale; +import org.geysermc.geyser.level.BedrockDimension; import org.geysermc.geyser.translator.level.block.entity.BedrockOnlyBlockEntity; import static org.geysermc.geyser.level.block.BlockStateValues.JAVA_AIR_ID; @UtilityClass public class ChunkUtils { - /** - * The minimum height Bedrock Edition will accept. - */ - public static final int MINIMUM_ACCEPTED_HEIGHT = 0; - public static final int MINIMUM_ACCEPTED_HEIGHT_OVERWORLD = -64; - /** - * The maximum chunk height Bedrock Edition will accept, from the lowest point to the highest. - */ - public static final int MAXIMUM_ACCEPTED_HEIGHT = 256; - public static final int MAXIMUM_ACCEPTED_HEIGHT_OVERWORLD = 384; - /** * An empty subchunk. */ @@ -249,17 +239,20 @@ public class ChunkUtils { throw new RuntimeException("Maximum Y must be a multiple of 16!"); } - int dimension = DimensionUtils.javaToBedrock(session.getDimension()); - boolean extendedHeight = dimension == 0; - session.getChunkCache().setExtendedHeight(extendedHeight); + BedrockDimension bedrockDimension = switch (session.getDimension()) { + case DimensionUtils.THE_END -> BedrockDimension.THE_END; + case DimensionUtils.NETHER -> DimensionUtils.isCustomBedrockNetherId() ? BedrockDimension.THE_END : BedrockDimension.THE_NETHER; + default -> BedrockDimension.OVERWORLD; + }; + session.getChunkCache().setBedrockDimension(bedrockDimension); // Yell in the console if the world height is too height in the current scenario // The constraints change depending on if the player is in the overworld or not, and if experimental height is enabled - if (minY < (extendedHeight ? MINIMUM_ACCEPTED_HEIGHT_OVERWORLD : MINIMUM_ACCEPTED_HEIGHT) - || maxY > (extendedHeight ? MAXIMUM_ACCEPTED_HEIGHT_OVERWORLD : MAXIMUM_ACCEPTED_HEIGHT)) { + // (Ignore this for the Nether. We can't change that at the moment without the workaround. :/ ) + if (minY < bedrockDimension.minY() || (bedrockDimension.doUpperHeightWarn() && maxY > bedrockDimension.height())) { session.getGeyser().getLogger().warning(GeyserLocale.getLocaleStringLog("geyser.network.translator.chunk.out_of_bounds", - extendedHeight ? MINIMUM_ACCEPTED_HEIGHT_OVERWORLD : MINIMUM_ACCEPTED_HEIGHT, - extendedHeight ? MAXIMUM_ACCEPTED_HEIGHT_OVERWORLD : MAXIMUM_ACCEPTED_HEIGHT, + String.valueOf(bedrockDimension.minY()), + String.valueOf(bedrockDimension.height()), session.getDimension())); } diff --git a/core/src/main/java/org/geysermc/geyser/util/DimensionUtils.java b/core/src/main/java/org/geysermc/geyser/util/DimensionUtils.java index 5af5e2c2b..f1aca63e8 100644 --- a/core/src/main/java/org/geysermc/geyser/util/DimensionUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/DimensionUtils.java @@ -109,7 +109,7 @@ public class DimensionUtils { // we check if the player is entering the nether and apply the nether fog to fake the fact that the client // thinks they are in the end dimension. if (BEDROCK_NETHER_ID == 2) { - if (bedrockDimension == BEDROCK_NETHER_ID) { + if (NETHER.equals(javaDimension)) { session.sendFog("minecraft:fog_hell"); } else if (previousDimension == BEDROCK_NETHER_ID) { session.removeFog("minecraft:fog_hell"); diff --git a/core/src/main/resources/bedrock/block_palette.1_17_40.nbt b/core/src/main/resources/bedrock/block_palette.1_18_0.nbt similarity index 100% rename from core/src/main/resources/bedrock/block_palette.1_17_40.nbt rename to core/src/main/resources/bedrock/block_palette.1_18_0.nbt diff --git a/core/src/main/resources/bedrock/block_palette.1_18_30.nbt b/core/src/main/resources/bedrock/block_palette.1_18_30.nbt new file mode 100644 index 0000000000000000000000000000000000000000..bf7690970f792f71b62d7b7f0c9a2f80a3292a21 GIT binary patch literal 45536 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 Date: Sun, 17 Apr 2022 20:10:16 -0400 Subject: [PATCH 082/110] Actually bump to 2.0.3-SNAPSHOT --- ap/pom.xml | 4 ++-- api/base/pom.xml | 2 +- api/geyser/pom.xml | 4 ++-- api/pom.xml | 2 +- bootstrap/bungeecord/pom.xml | 4 ++-- bootstrap/pom.xml | 4 ++-- bootstrap/spigot/pom.xml | 4 ++-- bootstrap/sponge/pom.xml | 4 ++-- bootstrap/standalone/pom.xml | 4 ++-- bootstrap/velocity/pom.xml | 4 ++-- common/pom.xml | 2 +- core/pom.xml | 8 ++++---- pom.xml | 2 +- 13 files changed, 24 insertions(+), 24 deletions(-) diff --git a/ap/pom.xml b/ap/pom.xml index 75f98275c..0644044a1 100644 --- a/ap/pom.xml +++ b/ap/pom.xml @@ -6,9 +6,9 @@ org.geysermc geyser-parent - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT ap - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT \ No newline at end of file diff --git a/api/base/pom.xml b/api/base/pom.xml index 37e97ef7e..1d051eaa3 100644 --- a/api/base/pom.xml +++ b/api/base/pom.xml @@ -5,7 +5,7 @@ org.geysermc api-parent - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT 4.0.0 diff --git a/api/geyser/pom.xml b/api/geyser/pom.xml index 9255bc579..2a933a2e0 100644 --- a/api/geyser/pom.xml +++ b/api/geyser/pom.xml @@ -5,7 +5,7 @@ org.geysermc api-parent - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT 4.0.0 @@ -26,7 +26,7 @@ org.geysermc base-api - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT compile diff --git a/api/pom.xml b/api/pom.xml index bae0da039..5d078fba5 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -6,7 +6,7 @@ org.geysermc geyser-parent - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT api-parent diff --git a/bootstrap/bungeecord/pom.xml b/bootstrap/bungeecord/pom.xml index f06a219bb..87e89c1f7 100644 --- a/bootstrap/bungeecord/pom.xml +++ b/bootstrap/bungeecord/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT bootstrap-bungeecord @@ -14,7 +14,7 @@ org.geysermc core - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT compile diff --git a/bootstrap/pom.xml b/bootstrap/pom.xml index 381f68bc2..7d6ac8f98 100644 --- a/bootstrap/pom.xml +++ b/bootstrap/pom.xml @@ -6,7 +6,7 @@ org.geysermc geyser-parent - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT bootstrap-parent pom @@ -34,7 +34,7 @@ org.geysermc ap - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT provided diff --git a/bootstrap/spigot/pom.xml b/bootstrap/spigot/pom.xml index 26f9c7083..98052d420 100644 --- a/bootstrap/spigot/pom.xml +++ b/bootstrap/spigot/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT bootstrap-spigot @@ -30,7 +30,7 @@ org.geysermc core - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT compile diff --git a/bootstrap/sponge/pom.xml b/bootstrap/sponge/pom.xml index 6285c6dbf..0718996ab 100644 --- a/bootstrap/sponge/pom.xml +++ b/bootstrap/sponge/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT bootstrap-sponge @@ -14,7 +14,7 @@ org.geysermc core - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT compile diff --git a/bootstrap/standalone/pom.xml b/bootstrap/standalone/pom.xml index 6babc6933..6fb45d800 100644 --- a/bootstrap/standalone/pom.xml +++ b/bootstrap/standalone/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT bootstrap-standalone @@ -18,7 +18,7 @@ org.geysermc core - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT compile diff --git a/bootstrap/velocity/pom.xml b/bootstrap/velocity/pom.xml index 1621d6ee6..93fb1441e 100644 --- a/bootstrap/velocity/pom.xml +++ b/bootstrap/velocity/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT bootstrap-velocity @@ -14,7 +14,7 @@ org.geysermc core - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT compile diff --git a/common/pom.xml b/common/pom.xml index a563b7aff..b3903f412 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -6,7 +6,7 @@ org.geysermc geyser-parent - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT common diff --git a/core/pom.xml b/core/pom.xml index 7dc64e08b..2b6956c1f 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -6,7 +6,7 @@ org.geysermc geyser-parent - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT core @@ -21,19 +21,19 @@ org.geysermc ap - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT provided org.geysermc geyser-api - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT compile org.geysermc common - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT compile diff --git a/pom.xml b/pom.xml index 0599716fe..99524e320 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 org.geysermc geyser-parent - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT pom Geyser Allows for players from Minecraft Bedrock Edition to join Minecraft Java Edition servers. From af08488d1eab4daf0b24156a95e527b69df4c194 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 18 Apr 2022 21:30:44 -0400 Subject: [PATCH 083/110] Fix message being sent still if a single escape character is sent --- .../protocol/bedrock/BedrockTextTranslator.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockTextTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockTextTranslator.java index 1a6771cc5..91ed5aa2b 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockTextTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockTextTranslator.java @@ -40,11 +40,8 @@ public class BedrockTextTranslator extends PacketTranslator { public void translate(GeyserSession session, TextPacket packet) { String message = packet.getMessage(); - if (message.isBlank()) { - // Java Edition (as of 1.17.1) just doesn't pass on these messages, so... we won't either! - return; - } - + // The order here is important - strip out illegal characters first, then check if it's blank + // (in case the message is blank after removing) if (message.indexOf(ChatColor.ESCAPE) != -1) { // Filter out all escape characters - Java doesn't let you type these StringBuilder builder = new StringBuilder(); @@ -57,6 +54,11 @@ public class BedrockTextTranslator extends PacketTranslator { message = builder.toString(); } + if (message.isBlank()) { + // Java Edition (as of 1.17.1) just doesn't pass on these messages, so... we won't either! + return; + } + if (MessageTranslator.isTooLong(message, session)) { return; } From 137eb3ece80382460d655929c72c803ac50036ab Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 19 Apr 2022 10:18:50 -0400 Subject: [PATCH 084/110] Replace instances of configs using `generateduuid` for Metrics --- .../configuration/GeyserJacksonConfiguration.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java b/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java index 463350441..03a3617e3 100644 --- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java @@ -35,9 +35,9 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import lombok.Getter; import lombok.Setter; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.network.CIDRMatcher; import org.geysermc.geyser.session.auth.AuthType; import org.geysermc.geyser.text.AsteriskSerializer; -import org.geysermc.geyser.network.CIDRMatcher; import org.geysermc.geyser.text.GeyserLocale; import java.io.IOException; @@ -240,8 +240,21 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration public static class MetricsInfo implements IMetricsInfo { private boolean enabled = true; + @JsonDeserialize(using = MetricsIdDeserializer.class) @JsonProperty("uuid") private String uniqueId = UUID.randomUUID().toString(); + + private static class MetricsIdDeserializer extends JsonDeserializer { + @Override + public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + String uuid = p.getValueAsString(); + if ("generateduuid".equals(uuid)) { + // Compensate for configs not copied from the jar + return UUID.randomUUID().toString(); + } + return uuid; + } + } } @JsonProperty("scoreboard-packet-threshold") From b528a1c4f6d2934234c5d33af0c6816b7c7a7c6f Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 20 Apr 2022 13:30:45 -0400 Subject: [PATCH 085/110] Update Protocol to better support older Netty versions --- core/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/pom.xml b/core/pom.xml index 2b6956c1f..7316afe0e 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -121,7 +121,7 @@ com.github.CloudburstMC.Protocol bedrock-v503 - 29ecd7a + f32c76d compile From e923325246eb2af3323762f6734723cb469a2d31 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 20 Apr 2022 21:22:02 -0400 Subject: [PATCH 086/110] Fix stonecutters for Bedrock 1.18.30 Also add an option in debug mode to not log pings in the event they're spammy. --- .../recipe/GeyserStonecutterData.java | 35 ++++++++++++++++ .../network/ConnectorServerEventHandler.java | 4 +- .../geyser/session/GeyserSession.java | 3 +- .../inventory/InventoryTranslator.java | 2 +- .../StonecutterInventoryTranslator.java | 41 +++++++------------ .../item/nbt/EnchantmentTranslator.java | 2 +- .../java/JavaUpdateRecipesTranslator.java | 14 ++++--- 7 files changed, 65 insertions(+), 36 deletions(-) create mode 100644 core/src/main/java/org/geysermc/geyser/inventory/recipe/GeyserStonecutterData.java diff --git a/core/src/main/java/org/geysermc/geyser/inventory/recipe/GeyserStonecutterData.java b/core/src/main/java/org/geysermc/geyser/inventory/recipe/GeyserStonecutterData.java new file mode 100644 index 000000000..04a772c31 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/inventory/recipe/GeyserStonecutterData.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2019-2022 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 com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; + +/** + * @param buttonId the button that needs to be pressed for Java Edition to accept this item. + * @param output the expected output of this item when cut. + */ +public record GeyserStonecutterData(int buttonId, ItemStack output) { +} diff --git a/core/src/main/java/org/geysermc/geyser/network/ConnectorServerEventHandler.java b/core/src/main/java/org/geysermc/geyser/network/ConnectorServerEventHandler.java index 892ddcb64..d41871cdb 100644 --- a/core/src/main/java/org/geysermc/geyser/network/ConnectorServerEventHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/ConnectorServerEventHandler.java @@ -47,6 +47,8 @@ import java.nio.charset.StandardCharsets; import java.util.List; public class ConnectorServerEventHandler implements BedrockServerEventHandler { + private static final boolean PRINT_DEBUG_PINGS = Boolean.parseBoolean(System.getProperty("Geyser.PrintPingsInDebugMode", "true")); + /* The following constants are all used to ensure the ping does not reach a length where it is unparsable by the Bedrock client */ @@ -88,7 +90,7 @@ public class ConnectorServerEventHandler implements BedrockServerEventHandler { @Override public BedrockPong onQuery(InetSocketAddress inetSocketAddress) { - if (geyser.getConfig().isDebugMode()) { + if (geyser.getConfig().isDebugMode() && PRINT_DEBUG_PINGS) { geyser.getLogger().debug(GeyserLocale.getLocaleStringLog("geyser.network.pinged", inetSocketAddress)); } diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index f35378af3..72eaaf0f7 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -98,6 +98,7 @@ import org.geysermc.geyser.entity.type.player.SkullPlayerEntity; 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.GeyserStonecutterData; import org.geysermc.geyser.level.WorldManager; import org.geysermc.geyser.level.physics.CollisionManager; import org.geysermc.geyser.network.netty.LocalSession; @@ -365,7 +366,7 @@ public class GeyserSession implements GeyserConnection, CommandSender { * The key is the Java ID of the item; the values are all the possible outputs' Java IDs sorted by their string identifier */ @Setter - private Int2ObjectMap stonecutterRecipes; + private Int2ObjectMap stonecutterRecipes; /** * Whether to work around 1.13's different behavior in villager trading menus. diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/InventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/InventoryTranslator.java index b48709595..6f4ca7ee4 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/InventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/InventoryTranslator.java @@ -144,7 +144,7 @@ public abstract class InventoryTranslator { /** * If {@link #shouldHandleRequestFirst(StackRequestActionData, Inventory)} returns true, this will be called */ - public ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { + protected ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { return rejectRequest(request); } diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/StonecutterInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/StonecutterInventoryTranslator.java index ae25a9ffd..e0e2e27bd 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/StonecutterInventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/StonecutterInventoryTranslator.java @@ -31,20 +31,14 @@ import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.Ser import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; import com.nukkitx.protocol.bedrock.data.inventory.ItemStackRequest; import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; -import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.CraftResultsDeprecatedStackRequestActionData; +import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.CraftRecipeStackRequestActionData; import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionData; import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionType; import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; -import it.unimi.dsi.fastutil.ints.IntList; -import org.geysermc.geyser.inventory.GeyserItemStack; -import org.geysermc.geyser.inventory.Inventory; -import org.geysermc.geyser.inventory.PlayerInventory; -import org.geysermc.geyser.inventory.StonecutterContainer; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.inventory.BedrockContainerSlot; -import org.geysermc.geyser.inventory.SlotType; +import org.geysermc.geyser.inventory.*; +import org.geysermc.geyser.inventory.recipe.GeyserStonecutterData; import org.geysermc.geyser.inventory.updater.UIInventoryUpdater; -import org.geysermc.geyser.translator.inventory.item.ItemTranslator; +import org.geysermc.geyser.session.GeyserSession; public class StonecutterInventoryTranslator extends AbstractBlockInventoryTranslator { public StonecutterInventoryTranslator() { @@ -53,31 +47,26 @@ public class StonecutterInventoryTranslator extends AbstractBlockInventoryTransl @Override protected boolean shouldHandleRequestFirst(StackRequestActionData action, Inventory inventory) { - // First is pre-1.18. TODO remove after 1.17.40 support is dropped and refactor stonecutter support to use CraftRecipeStackRequestActionData's recipe ID - return action.getType() == StackRequestActionType.CRAFT_NON_IMPLEMENTED_DEPRECATED || action.getType() == StackRequestActionType.CRAFT_RECIPE; + return action.getType() == StackRequestActionType.CRAFT_RECIPE; } @Override - public ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { - // TODO: Also surely to change in the future - StackRequestActionData data = request.getActions()[1]; - if (!(data instanceof CraftResultsDeprecatedStackRequestActionData craftData)) { + protected ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { + // Guarded by shouldHandleRequestFirst + CraftRecipeStackRequestActionData data = (CraftRecipeStackRequestActionData) request.getActions()[0]; + + // Look up all possible options of cutting from this ID + GeyserStonecutterData craftingData = session.getStonecutterRecipes().get(data.getRecipeNetworkId()); + if (craftingData == null) { return rejectRequest(request); } StonecutterContainer container = (StonecutterContainer) inventory; - // Get the ID of the item we are cutting - int id = inventory.getItem(0).getJavaId(); - // Look up all possible options of cutting from this ID - IntList results = session.getStonecutterRecipes().get(id); - if (results == null) { - return rejectRequest(request); - } - - ItemStack javaOutput = ItemTranslator.translateToJava(craftData.getResultItems()[0], session.getItemMappings()); - int button = results.indexOf(javaOutput.getId()); + int button = craftingData.buttonId(); // If we've already pressed the button with this item, no need to press it again! if (container.getStonecutterButton() != button) { + ItemStack javaOutput = craftingData.output(); + // Getting the index of the item in the Java stonecutter list ServerboundContainerButtonClickPacket packet = new ServerboundContainerButtonClickPacket(inventory.getId(), button); session.sendDownstreamPacket(packet); diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/EnchantmentTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/EnchantmentTranslator.java index 55d45f67e..cd6d5d6ff 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/EnchantmentTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/EnchantmentTranslator.java @@ -127,7 +127,7 @@ public class EnchantmentTranslator extends NbtItemStackTranslator { Enchantment enchantment = Enchantment.getByJavaIdentifier(((StringTag) javaEnchId).getValue()); if (enchantment == null) { - GeyserImpl.getInstance().getLogger().debug("Unknown java enchantment: " + javaEnchId.getValue()); + GeyserImpl.getInstance().getLogger().debug("Unknown Java enchantment while NBT item translating: " + javaEnchId.getValue()); return null; } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaUpdateRecipesTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaUpdateRecipesTranslator.java index 09ddfce4c..1c5a15b0b 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaUpdateRecipesTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaUpdateRecipesTranslator.java @@ -45,6 +45,7 @@ import lombok.EqualsAndHashCode; 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.GeyserStonecutterData; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; @@ -167,7 +168,7 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator stonecutterRecipeMap = new Int2ObjectOpenHashMap<>(); + Int2ObjectMap stonecutterRecipeMap = new Int2ObjectOpenHashMap<>(); for (Int2ObjectMap.Entry> data : unsortedStonecutterData.int2ObjectEntrySet()) { // Sort the list by each output item's Java identifier - this is how it's sorted on Java, and therefore // We can get the correct order for button pressing @@ -176,11 +177,13 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator new IntArrayList()); - outputs.add(stoneCuttingData.getResult().getId()); + // Add the net ID as the key and the button required + output for the value + stonecutterRecipeMap.put(netId++, new GeyserStonecutterData(buttonId++, javaOutput)); } } From 575fe98c0f67e71eef9479eef03b9d89e0b947c2 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 20 Apr 2022 21:39:35 -0400 Subject: [PATCH 087/110] Fix anvils for 1.18.30 Bedrock --- .../geyser/inventory/AnvilContainer.java | 37 +++++++++++++++++++ .../updater/AnvilInventoryUpdater.java | 8 ++-- .../inventory/AnvilInventoryTranslator.java | 27 ++++++++++++++ .../bedrock/BedrockFilterTextTranslator.java | 26 +------------ 4 files changed, 69 insertions(+), 29 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/inventory/AnvilContainer.java b/core/src/main/java/org/geysermc/geyser/inventory/AnvilContainer.java index a2b7ff9d6..688151a9e 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/AnvilContainer.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/AnvilContainer.java @@ -26,8 +26,14 @@ package org.geysermc.geyser.inventory; import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundRenameItemPacket; import lombok.Getter; import lombok.Setter; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.text.MessageTranslator; +import org.geysermc.geyser.util.ItemUtils; + +import javax.annotation.Nullable; /** * Used to determine if rename packets should be sent and stores @@ -48,6 +54,7 @@ public class AnvilContainer extends Container { /** * The new name of the item as received from Bedrock */ + @Nullable private String newName = null; private GeyserItemStack lastInput = GeyserItemStack.EMPTY; @@ -59,6 +66,36 @@ public class AnvilContainer extends Container { super(title, id, size, containerType, playerInventory); } + /** + * @return the name to use instead for renaming. + */ + public String checkForRename(GeyserSession session, String rename) { + String correctRename; + newName = rename; + + String originalName = ItemUtils.getCustomName(getInput().getNbt()); + + String plainOriginalName = MessageTranslator.convertToPlainText(originalName, session.getLocale()); + String plainNewName = MessageTranslator.convertToPlainText(rename, session.getLocale()); + if (!plainOriginalName.equals(plainNewName)) { + // Strip out formatting since Java Edition does not allow it + correctRename = plainNewName; + // Java Edition sends a packet every time an item is renamed even slightly in GUI. Fortunately, this works out for us now + ServerboundRenameItemPacket renameItemPacket = new ServerboundRenameItemPacket(plainNewName); + session.sendDownstreamPacket(renameItemPacket); + } else { + // Restore formatting for item since we're not renaming + correctRename = MessageTranslator.convertMessageLenient(originalName); + // Java Edition sends the original custom name when not renaming, + // if there isn't a custom name an empty string is sent + ServerboundRenameItemPacket renameItemPacket = new ServerboundRenameItemPacket(plainOriginalName); + session.sendDownstreamPacket(renameItemPacket); + } + + useJavaLevelCost = false; + return correctRename; + } + public GeyserItemStack getInput() { return getItem(0); } diff --git a/core/src/main/java/org/geysermc/geyser/inventory/updater/AnvilInventoryUpdater.java b/core/src/main/java/org/geysermc/geyser/inventory/updater/AnvilInventoryUpdater.java index d6f72b8d0..655d0f215 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/updater/AnvilInventoryUpdater.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/updater/AnvilInventoryUpdater.java @@ -384,19 +384,19 @@ public class AnvilInventoryUpdater extends InventoryUpdater { if (enchantTag.get("id") instanceof StringTag javaEnchId) { JavaEnchantment enchantment = JavaEnchantment.getByJavaIdentifier(javaEnchId.getValue()); if (enchantment == null) { - GeyserImpl.getInstance().getLogger().debug("Unknown java enchantment: " + javaEnchId.getValue()); + GeyserImpl.getInstance().getLogger().debug("Unknown Java enchantment in anvil: " + javaEnchId.getValue()); continue; } Tag javaEnchLvl = enchantTag.get("lvl"); - if (!(javaEnchLvl instanceof ShortTag || javaEnchLvl instanceof IntTag)) + if (javaEnchLvl == null || !(javaEnchLvl.getValue() instanceof Number number)) continue; // Handle duplicate enchantments if (bedrock) { - enchantments.putIfAbsent(enchantment, ((Number) javaEnchLvl.getValue()).intValue()); + enchantments.putIfAbsent(enchantment, number.intValue()); } else { - enchantments.mergeInt(enchantment, ((Number) javaEnchLvl.getValue()).intValue(), Math::max); + enchantments.mergeInt(enchantment, number.intValue(), Math::max); } } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/AnvilInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/AnvilInventoryTranslator.java index b09fcb7d4..e56586b14 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/AnvilInventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/AnvilInventoryTranslator.java @@ -27,7 +27,12 @@ package org.geysermc.geyser.translator.inventory; import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType; import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; +import com.nukkitx.protocol.bedrock.data.inventory.ItemStackRequest; import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; +import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.CraftRecipeOptionalStackRequestActionData; +import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionData; +import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionType; +import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; import org.geysermc.geyser.inventory.AnvilContainer; import org.geysermc.geyser.inventory.Inventory; import org.geysermc.geyser.inventory.PlayerInventory; @@ -35,12 +40,34 @@ import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.inventory.BedrockContainerSlot; import org.geysermc.geyser.inventory.updater.AnvilInventoryUpdater; +import java.util.Objects; + public class AnvilInventoryTranslator extends AbstractBlockInventoryTranslator { public AnvilInventoryTranslator() { super(3, "minecraft:anvil[facing=north]", com.nukkitx.protocol.bedrock.data.inventory.ContainerType.ANVIL, AnvilInventoryUpdater.INSTANCE, "minecraft:chipped_anvil", "minecraft:damaged_anvil"); } + @Override + protected boolean shouldHandleRequestFirst(StackRequestActionData action, Inventory inventory) { + return action.getType() == StackRequestActionType.CRAFT_RECIPE_OPTIONAL; + } + + @Override + protected ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { + // Guarded by shouldHandleRequestFirst check + CraftRecipeOptionalStackRequestActionData data = (CraftRecipeOptionalStackRequestActionData) request.getActions()[0]; + AnvilContainer container = (AnvilContainer) inventory; + + // Required as of 1.18.30 - FilterTextPackets no longer appear to be sent + String name = request.getFilterStrings()[data.getFilteredStringIndex()]; + if (!Objects.equals(name, container.getNewName())) { + container.checkForRename(session, name); + } + + return super.translateRequest(session, inventory, request); + } + @Override public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) { return switch (slotInfoData.getContainer()) { diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockFilterTextTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockFilterTextTranslator.java index 818829e8f..4e729bc59 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockFilterTextTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockFilterTextTranslator.java @@ -25,15 +25,12 @@ package org.geysermc.geyser.translator.protocol.bedrock; -import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundRenameItemPacket; import com.nukkitx.protocol.bedrock.packet.FilterTextPacket; import org.geysermc.geyser.inventory.AnvilContainer; import org.geysermc.geyser.inventory.CartographyContainer; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; -import org.geysermc.geyser.translator.text.MessageTranslator; -import org.geysermc.geyser.util.ItemUtils; /** * Used to send strings to the server and filter out unwanted words. @@ -50,28 +47,7 @@ public class BedrockFilterTextTranslator extends PacketTranslator Date: Thu, 21 Apr 2022 14:23:02 -0400 Subject: [PATCH 088/110] Update bug_report.yml Request the device that the Bedrock player is experiencing the bug on --- .github/ISSUE_TEMPLATE/bug_report.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index f4a0e21ff..036d838ef 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -55,9 +55,9 @@ body: required: true - type: input attributes: - label: "Minecraft: Bedrock Edition Version" - description: "What version of Minecraft: Bedrock Edition are you using? Leave empty if the bug happens before you can connect with Minecraft: Bedrock Edition." - placeholder: "For example: 1.16.201" + label: "Minecraft: Bedrock Edition Device/Version" + description: "What version of Minecraft: Bedrock Edition are you using, and what device(s) does the bug occur on? Leave empty if the bug happens before you can connect with Minecraft: Bedrock Edition." + placeholder: "For example: 1.16.201, Nintendo Switch" - type: textarea attributes: label: Additional Context From 05d74ebd3efc89a7e92e27feaf91c81d2ac050e4 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 21 Apr 2022 22:23:00 -0400 Subject: [PATCH 089/110] Fix signs for Bedrock 1.18.30 Fixes #2944 --- .../protocol/bedrock/BedrockBlockEntityDataTranslator.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockBlockEntityDataTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockBlockEntityDataTranslator.java index d00914fb1..6da4539cf 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockBlockEntityDataTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockBlockEntityDataTranslator.java @@ -30,6 +30,7 @@ import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.Ser import com.github.steveice10.mc.protocol.packet.ingame.serverbound.level.ServerboundSignUpdatePacket; import com.nukkitx.nbt.NbtMap; import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket; +import com.nukkitx.protocol.bedrock.v503.Bedrock_v503; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; @@ -40,6 +41,7 @@ public class BedrockBlockEntityDataTranslator extends PacketTranslator Date: Thu, 21 Apr 2022 22:24:41 -0400 Subject: [PATCH 090/110] oops --- .../protocol/bedrock/BedrockBlockEntityDataTranslator.java | 1 - 1 file changed, 1 deletion(-) diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockBlockEntityDataTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockBlockEntityDataTranslator.java index 6da4539cf..1f2f29ea5 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockBlockEntityDataTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockBlockEntityDataTranslator.java @@ -41,7 +41,6 @@ public class BedrockBlockEntityDataTranslator extends PacketTranslator Date: Fri, 22 Apr 2022 18:06:38 -0400 Subject: [PATCH 091/110] Don't send the SetHealthPacket clientbound Seems like this can cause the client to break in 1.18.30, and we already send the health as an attribute. --- .../java/entity/player/JavaSetHealthTranslator.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaSetHealthTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaSetHealthTranslator.java index f21823b07..d989fe964 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaSetHealthTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaSetHealthTranslator.java @@ -27,7 +27,6 @@ package org.geysermc.geyser.translator.protocol.java.entity.player; import com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.player.ClientboundSetHealthPacket; import com.nukkitx.protocol.bedrock.data.AttributeData; -import com.nukkitx.protocol.bedrock.packet.SetHealthPacket; import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket; import org.geysermc.geyser.entity.attribute.GeyserAttributeType; import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; @@ -44,11 +43,6 @@ public class JavaSetHealthTranslator extends PacketTranslator Date: Fri, 22 Apr 2022 20:16:22 -0400 Subject: [PATCH 092/110] Make completed advancement color easier to read Resolves #2937 --- .../geysermc/geyser/session/cache/AdvancementsCache.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/AdvancementsCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/AdvancementsCache.java index 9d3e4f5aa..0df842d1d 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/AdvancementsCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/AdvancementsCache.java @@ -30,6 +30,7 @@ import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.Ser import lombok.Getter; import lombok.Setter; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.translator.text.MessageTranslator; import org.geysermc.geyser.level.GeyserAdvancement; import org.geysermc.geyser.text.GeyserLocale; @@ -140,7 +141,7 @@ public class AdvancementsCache { if (advancement != null) { if (advancement.getParentId() != null && currentAdvancementCategoryId.equals(advancement.getRootId(this))) { boolean color = isEarned(advancement) || !advancement.getDisplayData().isShowToast(); - builder.button((color ? "§6" : "") + MessageTranslator.convertMessage(advancement.getDisplayData().getTitle()) + '\n'); + builder.button((color ? ChatColor.DARK_GREEN : "") + MessageTranslator.convertMessage(advancement.getDisplayData().getTitle()) + '\n'); } } } @@ -266,10 +267,9 @@ public class AdvancementsCache { } public String getColorFromAdvancementFrameType(GeyserAdvancement advancement) { - String base = "\u00a7"; if (advancement.getDisplayData().getFrameType() == Advancement.DisplayData.FrameType.CHALLENGE) { - return base + "5"; + return ChatColor.DARK_PURPLE; } - return base + "a"; // Used for types TASK and GOAL + return ChatColor.GREEN; // Used for types TASK and GOAL } } From 4cb18ebf5b483de1e4ac4a211c40f07659c44fd9 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 23 Apr 2022 14:14:09 -0400 Subject: [PATCH 093/110] Use stable version for maven-shade-plugin --- bootstrap/bungeecord/pom.xml | 2 +- bootstrap/spigot/pom.xml | 2 +- bootstrap/sponge/pom.xml | 2 +- bootstrap/standalone/pom.xml | 2 +- bootstrap/velocity/pom.xml | 2 +- pom.xml | 13 ------------- 6 files changed, 5 insertions(+), 18 deletions(-) diff --git a/bootstrap/bungeecord/pom.xml b/bootstrap/bungeecord/pom.xml index 87e89c1f7..b2c661447 100644 --- a/bootstrap/bungeecord/pom.xml +++ b/bootstrap/bungeecord/pom.xml @@ -49,7 +49,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.3.0-SNAPSHOT + 3.3.0 package diff --git a/bootstrap/spigot/pom.xml b/bootstrap/spigot/pom.xml index 98052d420..e9e7687f4 100644 --- a/bootstrap/spigot/pom.xml +++ b/bootstrap/spigot/pom.xml @@ -87,7 +87,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.3.0-SNAPSHOT + 3.3.0 package diff --git a/bootstrap/sponge/pom.xml b/bootstrap/sponge/pom.xml index 0718996ab..8eba4d73d 100644 --- a/bootstrap/sponge/pom.xml +++ b/bootstrap/sponge/pom.xml @@ -48,7 +48,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.3.0-SNAPSHOT + 3.3.0 package diff --git a/bootstrap/standalone/pom.xml b/bootstrap/standalone/pom.xml index 6fb45d800..8ee94b793 100644 --- a/bootstrap/standalone/pom.xml +++ b/bootstrap/standalone/pom.xml @@ -93,7 +93,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.3.0-SNAPSHOT + 3.3.0 com.github.edwgiz diff --git a/bootstrap/velocity/pom.xml b/bootstrap/velocity/pom.xml index 93fb1441e..36882a19e 100644 --- a/bootstrap/velocity/pom.xml +++ b/bootstrap/velocity/pom.xml @@ -48,7 +48,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.3.0-SNAPSHOT + 3.3.0 package diff --git a/pom.xml b/pom.xml index 99524e320..f4930959d 100644 --- a/pom.xml +++ b/pom.xml @@ -40,20 +40,7 @@ core - - - - apache.snapshots - https://repository.apache.org/snapshots/ - - - - - - apache.snapshots - https://repository.apache.org/snapshots/ - jitpack.io https://jitpack.io From 2f54bf0e14b50d4887566dcb30e17629e0f0ec4d Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sat, 23 Apr 2022 20:57:32 +0200 Subject: [PATCH 094/110] Rotation fixes (#2396) * Should fix some rotation issues * Some more changes * Small changes * Fixed merge conflicts and updated other classes that changed * Added translation for the LookAt packet --- .../entity/type/AbstractArrowEntity.java | 6 +- .../geyser/entity/type/BoatEntity.java | 4 +- .../geysermc/geyser/entity/type/Entity.java | 15 ++-- .../geyser/entity/type/FireballEntity.java | 2 +- .../geyser/entity/type/FishingHookEntity.java | 4 +- .../geyser/entity/type/ItemEntity.java | 8 +-- .../geyser/entity/type/MinecartEntity.java | 2 +- .../geyser/entity/type/ThrowableEntity.java | 14 ++-- .../entity/type/living/ArmorStandEntity.java | 31 ++++----- .../entity/type/living/SquidEntity.java | 2 +- .../living/monster/EnderDragonEntity.java | 10 +-- .../entity/type/player/PlayerEntity.java | 17 +++-- .../player/BedrockMovePlayerTranslator.java | 10 +-- .../player/JavaPlayerLookAtTranslator.java | 68 +++++++++++++++++++ .../player/JavaPlayerPositionTranslator.java | 10 ++- .../org/geysermc/geyser/util/MathUtils.java | 19 +++++- 16 files changed, 154 insertions(+), 68 deletions(-) create mode 100644 core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerLookAtTranslator.java diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/AbstractArrowEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/AbstractArrowEntity.java index db0cfc738..963e0b70a 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/AbstractArrowEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/AbstractArrowEntity.java @@ -70,8 +70,8 @@ public class AbstractArrowEntity extends Entity { super.setMotion(motion); double horizontalSpeed = Math.sqrt(motion.getX() * motion.getX() + motion.getZ() * motion.getZ()); - this.yaw = (float) Math.toDegrees(Math.atan2(motion.getX(), motion.getZ())); - this.pitch = (float) Math.toDegrees(Math.atan2(motion.getY(), horizontalSpeed)); - this.headYaw = yaw; + setYaw((float) Math.toDegrees(Math.atan2(motion.getX(), motion.getZ()))); + setPitch((float) Math.toDegrees(Math.atan2(motion.getY(), horizontalSpeed))); + setHeadYaw(getYaw()); } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/BoatEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/BoatEntity.java index 6ce490bc2..9fd96f46b 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/BoatEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/BoatEntity.java @@ -81,8 +81,8 @@ public class BoatEntity extends Entity { public void moveAbsolute(Vector3f position, float yaw, float pitch, float headYaw, boolean isOnGround, boolean teleported) { // We don't include the rotation (y) as it causes the boat to appear sideways setPosition(position.add(0d, this.definition.offset(), 0d)); - this.yaw = yaw + 90; - this.headYaw = yaw + 90; + setYaw(yaw + 90); + setHeadYaw(yaw + 90); setOnGround(isOnGround); MoveEntityAbsolutePacket moveEntityPacket = new MoveEntityAbsolutePacket(); diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java b/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java index 270f69ee0..4dc3a437a 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java @@ -204,7 +204,7 @@ public class Entity { } public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, boolean isOnGround) { - moveRelative(relX, relY, relZ, yaw, pitch, this.headYaw, isOnGround); + moveRelative(relX, relY, relZ, yaw, pitch, getHeadYaw(), isOnGround); } public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, float headYaw, boolean isOnGround) { @@ -225,7 +225,7 @@ public class Entity { } public void moveAbsolute(Vector3f position, float yaw, float pitch, boolean isOnGround, boolean teleported) { - moveAbsolute(position, yaw, pitch, this.headYaw, isOnGround, teleported); + moveAbsolute(position, yaw, pitch, getHeadYaw(), isOnGround, teleported); } public void moveAbsolute(Vector3f position, float yaw, float pitch, float headYaw, boolean isOnGround, boolean teleported) { @@ -254,7 +254,8 @@ public class Entity { * @param isOnGround Whether the entity is currently on the ground. */ public void teleport(Vector3f position, float yaw, float pitch, boolean isOnGround) { - moveAbsolute(position, yaw, pitch, isOnGround, false); + // teleport will always set the headYaw to yaw + moveAbsolute(position, yaw, pitch, yaw, isOnGround, false); } /** @@ -262,7 +263,7 @@ public class Entity { * @param headYaw The new head rotation of the entity. */ public void updateHeadLookRotation(float headYaw) { - moveRelative(0, 0, 0, headYaw, pitch, this.headYaw, onGround); + moveRelative(0, 0, 0, getYaw(), getPitch(), headYaw, isOnGround()); } /** @@ -275,7 +276,7 @@ public class Entity { * @param isOnGround Whether the entity is currently on the ground. */ public void updatePositionAndRotation(double moveX, double moveY, double moveZ, float yaw, float pitch, boolean isOnGround) { - moveRelative(moveX, moveY, moveZ, this.yaw, pitch, yaw, isOnGround); + moveRelative(moveX, moveY, moveZ, yaw, pitch, getHeadYaw(), isOnGround); } /** @@ -436,12 +437,12 @@ public class Entity { } /** - * x = Pitch, y = HeadYaw, z = Yaw + * x = Pitch, y = Yaw, z = HeadYaw * * @return the bedrock rotation */ public Vector3f getBedrockRotation() { - return Vector3f.from(pitch, headYaw, yaw); + return Vector3f.from(getPitch(), getYaw(), getHeadYaw()); } /** diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/FireballEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/FireballEntity.java index 744ddf4a6..135f58906 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/FireballEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/FireballEntity.java @@ -72,6 +72,6 @@ public class FireballEntity extends ThrowableEntity { @Override public void tick() { - moveAbsoluteImmediate(tickMovement(position), yaw, pitch, headYaw, false, false); + moveAbsoluteImmediate(tickMovement(position), getYaw(), getPitch(), getHeadYaw(), false, false); } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/FishingHookEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/FishingHookEntity.java index 2f5590c37..52ad82370 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/FishingHookEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/FishingHookEntity.java @@ -152,7 +152,7 @@ public class FishingHookEntity extends ThrowableEntity { float gravity = getGravity(); motion = motion.down(gravity); - moveAbsoluteImmediate(position.add(motion), yaw, pitch, headYaw, onGround, false); + moveAbsoluteImmediate(position.add(motion), getYaw(), getPitch(), getHeadYaw(), isOnGround(), false); float drag = getDrag(); motion = motion.mul(drag); @@ -160,7 +160,7 @@ public class FishingHookEntity extends ThrowableEntity { @Override protected float getGravity() { - if (!isInWater() && !onGround) { + if (!isInWater() && !isOnGround()) { return 0.03f; } return 0; diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/ItemEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/ItemEntity.java index 79ffe68ef..f36a7c732 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/ItemEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/ItemEntity.java @@ -76,10 +76,10 @@ public class ItemEntity extends ThrowableEntity { if (isInWater()) { return; } - if (!onGround || (motion.getX() * motion.getX() + motion.getZ() * motion.getZ()) > 0.00001) { + if (!isOnGround() || (motion.getX() * motion.getX() + motion.getZ() * motion.getZ()) > 0.00001) { float gravity = getGravity(); motion = motion.down(gravity); - moveAbsoluteImmediate(position.add(motion), yaw, pitch, headYaw, onGround, false); + moveAbsoluteImmediate(position.add(motion), getYaw(), getPitch(), getHeadYaw(), isOnGround(), false); float drag = getDrag(); motion = motion.mul(drag, 0.98f, drag); } @@ -124,7 +124,7 @@ public class ItemEntity extends ThrowableEntity { @Override protected float getGravity() { - if (getFlag(EntityFlag.HAS_GRAVITY) && !onGround && !isInWater()) { + if (getFlag(EntityFlag.HAS_GRAVITY) && !isOnGround() && !isInWater()) { // Gravity can change if the item is in water/lava, but // the server calculates the motion & position for us return 0.04f; @@ -134,7 +134,7 @@ public class ItemEntity extends ThrowableEntity { @Override protected float getDrag() { - if (onGround) { + if (isOnGround()) { Vector3i groundBlockPos = position.toInt().down(1); int blockState = session.getGeyser().getWorldManager().getBlockAt(session, groundBlockPos); return BlockStateValues.getSlipperiness(blockState) * 0.98f; diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/MinecartEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/MinecartEntity.java index a427d6a43..6f722864b 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/MinecartEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/MinecartEntity.java @@ -66,7 +66,7 @@ public class MinecartEntity extends Entity { @Override public Vector3f getBedrockRotation() { // Note: minecart rotation on rails does not care about the actual rotation value - return Vector3f.from(0, yaw, 0); + return Vector3f.from(0, getYaw(), 0); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/ThrowableEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/ThrowableEntity.java index 87e3be405..ad8b60bdb 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/ThrowableEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/ThrowableEntity.java @@ -56,7 +56,7 @@ public class ThrowableEntity extends Entity implements Tickable { */ @Override public void tick() { - moveAbsoluteImmediate(position.add(motion), yaw, pitch, headYaw, onGround, false); + moveAbsoluteImmediate(position.add(motion), getYaw(), getPitch(), getHeadYaw(), isOnGround(), false); float drag = getDrag(); float gravity = getGravity(); motion = motion.mul(drag).down(gravity); @@ -89,20 +89,20 @@ public class ThrowableEntity extends Entity implements Tickable { } setPosition(position); - if (this.yaw != yaw) { + if (getYaw() != yaw) { moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_YAW); moveEntityDeltaPacket.setYaw(yaw); - this.yaw = yaw; + setYaw(yaw); } - if (this.pitch != pitch) { + if (getPitch() != pitch) { moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_PITCH); moveEntityDeltaPacket.setPitch(pitch); - this.pitch = pitch; + setPitch(pitch); } - if (this.headYaw != headYaw) { + if (getHeadYaw() != headYaw) { moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_HEAD_YAW); moveEntityDeltaPacket.setHeadYaw(headYaw); - this.headYaw = headYaw; + setHeadYaw(headYaw); } if (!moveEntityDeltaPacket.getFlags().isEmpty()) { diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/ArmorStandEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/ArmorStandEntity.java index 9c7e6d107..18076763e 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/ArmorStandEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/ArmorStandEntity.java @@ -42,6 +42,7 @@ import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.type.LivingEntity; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.MathUtils; import java.util.Optional; import java.util.UUID; @@ -87,8 +88,6 @@ public class ArmorStandEntity extends LivingEntity { @Override public void spawnEntity() { - this.pitch = yaw; - this.headYaw = yaw; super.spawnEntity(); } @@ -205,9 +204,9 @@ public class ArmorStandEntity extends LivingEntity { // Indicate that rotation should be checked setFlag(EntityFlag.BRIBED, true); - int rotationX = getRotation(rotation.getPitch()); - int rotationY = getRotation(rotation.getYaw()); - int rotationZ = getRotation(rotation.getRoll()); + int rotationX = MathUtils.wrapDegreesToInt(rotation.getPitch()); + int rotationY = MathUtils.wrapDegreesToInt(rotation.getYaw()); + int rotationZ = MathUtils.wrapDegreesToInt(rotation.getRoll()); // The top bit acts like binary and determines if each rotation goes above 100 // We don't do this for the negative values out of concerns of the number being too big int topBit = (Math.abs(rotationX) >= 100 ? 4 : 0) + (Math.abs(rotationY) >= 100 ? 2 : 0) + (Math.abs(rotationZ) >= 100 ? 1 : 0); @@ -319,7 +318,7 @@ public class ArmorStandEntity extends LivingEntity { // Create the second entity. It doesn't need to worry about the items, but it does need to worry about // the metadata as it will hold the name tag. secondEntity = new ArmorStandEntity(session, 0, session.getEntityCache().getNextEntityId().incrementAndGet(), null, - EntityDefinitions.ARMOR_STAND, position, motion, yaw, pitch, headYaw); + EntityDefinitions.ARMOR_STAND, position, motion, getYaw(), getPitch(), getHeadYaw()); secondEntity.primaryEntity = false; if (!this.positionRequiresOffset) { // Ensure the offset is applied for the 0 scale @@ -375,17 +374,6 @@ public class ArmorStandEntity extends LivingEntity { } } - private int getRotation(float rotation) { - rotation = rotation % 360f; - if (rotation < -180f) { - rotation += 360f; - } else if (rotation >= 180f) { - // 181 -> -179 - rotation = -(180 - (rotation - 180)); - } - return (int) rotation; - } - /** * If this armor stand is not a marker, set its bounding box size and scale. */ @@ -439,9 +427,14 @@ public class ArmorStandEntity extends LivingEntity { MoveEntityAbsolutePacket moveEntityPacket = new MoveEntityAbsolutePacket(); moveEntityPacket.setRuntimeEntityId(geyserId); moveEntityPacket.setPosition(position); - moveEntityPacket.setRotation(Vector3f.from(yaw, yaw, yaw)); - moveEntityPacket.setOnGround(onGround); + moveEntityPacket.setRotation(getBedrockRotation()); + moveEntityPacket.setOnGround(isOnGround()); moveEntityPacket.setTeleported(false); session.sendUpstreamPacket(moveEntityPacket); } + + @Override + public Vector3f getBedrockRotation() { + return Vector3f.from(getYaw(), getYaw(), getYaw()); + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/SquidEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/SquidEntity.java index c81cf68de..552f6a46c 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/SquidEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/SquidEntity.java @@ -117,7 +117,7 @@ public class SquidEntity extends WaterEntity implements Tickable { @Override public Vector3f getBedrockRotation() { - return Vector3f.from(pitch, yaw, yaw); + return Vector3f.from(getPitch(), getYaw(), getYaw()); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonEntity.java index 0069bfb5b..99ab1a55c 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonEntity.java @@ -130,7 +130,7 @@ public class EnderDragonEntity extends MobEntity implements Tickable { for (int i = 0; i < segmentHistory.length; i++) { segmentHistory[i] = new Segment(); - segmentHistory[i].yaw = headYaw; + segmentHistory[i].yaw = getHeadYaw(); segmentHistory[i].y = position.getY(); } } @@ -168,7 +168,7 @@ public class EnderDragonEntity extends MobEntity implements Tickable { * Updates the positions of the Ender Dragon's multiple bounding boxes */ private void updateBoundingBoxes() { - Vector3f facingDir = Vector3f.createDirectionDeg(0, headYaw); + Vector3f facingDir = Vector3f.createDirectionDeg(0, getHeadYaw()); Segment baseSegment = getSegment(5); // Used to angle the head, neck, and tail when the dragon flies up/down float pitch = (float) Math.toRadians(10 * (baseSegment.getY() - getSegment(10).getY())); @@ -187,7 +187,7 @@ public class EnderDragonEntity extends MobEntity implements Tickable { neck.setPosition(facingDir.up(pitchY).mul(pitchXZ, 1, -pitchXZ).mul(5.5f).up(headDuck)); body.setPosition(facingDir.mul(0.5f, 0f, -0.5f)); - Vector3f wingPos = Vector3f.createDirectionDeg(0, 90f - headYaw).mul(4.5f).up(2f); + Vector3f wingPos = Vector3f.createDirectionDeg(0, 90f - getHeadYaw()).mul(4.5f).up(2f); rightWing.setPosition(wingPos); leftWing.setPosition(wingPos.mul(-1, 1, -1)); // Mirror horizontally @@ -196,7 +196,7 @@ public class EnderDragonEntity extends MobEntity implements Tickable { float distance = (i + 1) * 2f; // Curls the tail when the dragon turns Segment targetSegment = getSegment(12 + 2 * i); - float angle = headYaw + targetSegment.yaw - baseSegment.yaw; + float angle = getHeadYaw() + targetSegment.yaw - baseSegment.yaw; float tailYOffset = targetSegment.y - baseSegment.y - (distance + 1.5f) * pitchY + 1.5f; tail[i].setPosition(Vector3f.createDirectionDeg(0, angle).mul(distance).add(tailBase).mul(-pitchXZ, 1, pitchXZ).up(tailYOffset)); @@ -306,7 +306,7 @@ public class EnderDragonEntity extends MobEntity implements Tickable { */ private void pushSegment() { latestSegment = (latestSegment + 1) % segmentHistory.length; - segmentHistory[latestSegment].yaw = headYaw; + segmentHistory[latestSegment].yaw = getHeadYaw(); segmentHistory[latestSegment].y = position.getY(); } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java index 0d6c0dac1..5c0b18838 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java @@ -213,7 +213,7 @@ public class PlayerEntity extends LivingEntity { @Override public void updateHeadLookRotation(float headYaw) { - moveRelative(0, 0, 0, yaw, pitch, headYaw, onGround); + moveRelative(0, 0, 0, getYaw(), getPitch(), headYaw, isOnGround()); MovePlayerPacket movePlayerPacket = new MovePlayerPacket(); movePlayerPacket.setRuntimeEntityId(geyserId); movePlayerPacket.setPosition(position); @@ -233,9 +233,11 @@ public class PlayerEntity extends LivingEntity { } } - @Override - public void updateRotation(float yaw, float pitch, boolean isOnGround) { - super.updateRotation(yaw, pitch, isOnGround); + public void updateRotation(float yaw, float pitch, float headYaw, boolean isOnGround) { + // the method below is called by super.updateRotation(yaw, pitch, isOnGround). + // but we have to be able to set the headYaw, so we call the method below directly. + super.moveRelative(0, 0, 0, yaw, pitch, headYaw, isOnGround); + // Both packets need to be sent or else player head rotation isn't correctly updated MovePlayerPacket movePlayerPacket = new MovePlayerPacket(); movePlayerPacket.setRuntimeEntityId(geyserId); @@ -252,6 +254,11 @@ public class PlayerEntity extends LivingEntity { } } + @Override + public void updateRotation(float yaw, float pitch, boolean isOnGround) { + updateRotation(yaw, pitch, getHeadYaw(), isOnGround); + } + @Override public void setPosition(Vector3f position) { super.setPosition(position.add(0, definition.offset(), 0)); @@ -300,7 +307,7 @@ public class PlayerEntity extends LivingEntity { } // The parrot is a separate entity in Bedrock, but part of the player entity in Java //TODO is a UUID provided in NBT? ParrotEntity parrot = new ParrotEntity(session, 0, session.getEntityCache().getNextEntityId().incrementAndGet(), - null, EntityDefinitions.PARROT, position, motion, yaw, pitch, headYaw); + null, EntityDefinitions.PARROT, position, motion, getYaw(), getPitch(), getHeadYaw()); parrot.spawnEntity(); parrot.getDirtyMetadata().put(EntityData.VARIANT, tag.get("Variant").getValue()); // Different position whether the parrot is left or right diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockMovePlayerTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockMovePlayerTranslator.java index a63c0f334..8732b7909 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockMovePlayerTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockMovePlayerTranslator.java @@ -81,8 +81,7 @@ public class BedrockMovePlayerTranslator extends PacketTranslator { + @Override + public void translate(GeyserSession session, ClientboundPlayerLookAtPacket packet) { + var targetPosition = targetPosition(session, packet); + var selfPosition = session.getPlayerEntity().getPosition(); + + var xDelta = targetPosition.getX() - selfPosition.getX(); + var yDelta = targetPosition.getY() - selfPosition.getY(); + var zDelta = targetPosition.getZ() - selfPosition.getZ(); + var sqrt = Math.sqrt(xDelta * xDelta + zDelta * zDelta); + + var yaw = MathUtils.wrapDegrees(-Math.toDegrees(Math.atan2(yDelta, sqrt))); + var pitch = MathUtils.wrapDegrees(Math.toDegrees(Math.atan2(zDelta, xDelta)) - 90.0); + + var self = session.getPlayerEntity(); + // headYaw is also set to yaw in this packet + self.updateRotation(yaw, pitch, yaw, self.isOnGround()); + } + + public Vector3f targetPosition(GeyserSession session, ClientboundPlayerLookAtPacket packet) { + if (packet.getTargetEntityOrigin() != null) { + var entityId = packet.getTargetEntityId(); + var target = session.getEntityCache().getEntityByJavaId(entityId); + if (target != null) { + return switch (packet.getTargetEntityOrigin()) { + case FEET -> target.getPosition(); + case EYES -> target.getPosition().add(0, target.getBoundingBoxHeight(), 0); + }; + } + } + return Vector3f.from(packet.getX(), packet.getY(), packet.getZ()); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerPositionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerPositionTranslator.java index 97487ea6a..f5d21ecc9 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerPositionTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerPositionTranslator.java @@ -74,7 +74,7 @@ public class JavaPlayerPositionTranslator extends PacketTranslator= 180.0f) { + degrees -= 360.0f; + } + return degrees; + } + + public static float wrapDegrees(double degrees) { + return wrapDegrees((float) degrees); + } + + public static int wrapDegreesToInt(float degrees) { + return (int) wrapDegrees(degrees); + } + /** * Round the given float to the next whole number * From 9bf3334cb018a62cba2f0b192f1593e4799741c8 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 24 Apr 2022 13:38:44 -0400 Subject: [PATCH 095/110] Update Protocol for 1.18.30 command param correctness --- core/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/pom.xml b/core/pom.xml index 7316afe0e..adc841ebe 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -121,7 +121,7 @@ com.github.CloudburstMC.Protocol bedrock-v503 - f32c76d + 2a344e4 compile From 0d1fedbdbfa0a1968ace97ea0435b630d6c4d31c Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 24 Apr 2022 13:58:34 -0400 Subject: [PATCH 096/110] Fix instances of resizing global biome palettes Fixes #2744 --- .../geysermc/geyser/translator/level/BiomeTranslator.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/BiomeTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/BiomeTranslator.java index a202c3f81..ac9a0517a 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/level/BiomeTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/BiomeTranslator.java @@ -126,11 +126,10 @@ public class BiomeTranslator { storage = new BlockStorage(bitArray, bedrockPalette); } else { storage = new BlockStorage(0); - BitArray bitArray = storage.getBitArray(); // Each section of biome corresponding to a chunk section contains 4 * 4 * 4 entries for (int i = 0; i < 64; i++) { - int javaId = biomeData.getPalette().idToState(biomeData.getStorage().get(i)); + int javaId = palette.idToState(biomeData.getStorage().get(i)); int x = i & 3; int y = (i >> 4) & 3; int z = (i >> 2) & 3; @@ -139,7 +138,9 @@ public class BiomeTranslator { int idx = storage.idFor(biomeId); // Convert biome coordinates into block coordinates // Bedrock expects a full 4096 blocks - multiplyIdToStorage(bitArray, idx, x, y, z); + // Implementation note: storage.getBitArray() must be called and not stored - if the palette + // grows, then the instance can change + multiplyIdToStorage(storage.getBitArray(), idx, x, y, z); } } return storage; From 074d60d5b02cd0d0fc8deafe1a658f4aec15bdbd Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 25 Apr 2022 15:13:09 -0400 Subject: [PATCH 097/110] Add system locale and encoding to Geyser dumps --- .../src/main/java/org/geysermc/geyser/dump/DumpInfo.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java b/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java index 2734c7443..ca902ea5c 100644 --- a/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java +++ b/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java @@ -54,10 +54,7 @@ import java.net.InetSocketAddress; import java.net.Socket; import java.net.UnknownHostException; import java.nio.file.Paths; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Properties; +import java.util.*; import java.util.stream.Collectors; @Getter @@ -67,6 +64,8 @@ public class DumpInfo { private final DumpInfo.VersionInfo versionInfo; private final int cpuCount; + private final Locale systemLocale; + private final String systemEncoding; private Properties gitInfo; private final GeyserConfiguration config; private final Floodgate floodgate; @@ -81,6 +80,8 @@ public class DumpInfo { this.versionInfo = new VersionInfo(); this.cpuCount = Runtime.getRuntime().availableProcessors(); + this.systemLocale = Locale.getDefault(); + this.systemEncoding = System.getProperty("file.encoding"); try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResource("git.properties")) { this.gitInfo = new Properties(); From 3035527be21583a207849bef7a3bce5ba6b790f8 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 27 Apr 2022 20:04:13 -0400 Subject: [PATCH 098/110] Indicate 1.18.31 support for Geyser This uses the same protocol version as 1.18.30, so no further changes are required. --- README.md | 2 +- .../java/org/geysermc/geyser/network/MinecraftProtocol.java | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bbb9532a5..3e247f4b5 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have joined us here! -### Currently supporting Minecraft Bedrock 1.18.0 - 1.18.30 and Minecraft Java 1.18.2. +### Currently supporting Minecraft Bedrock 1.18.0 - 1.18.31 and Minecraft Java 1.18.2. ## Setting Up Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Geyser. diff --git a/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java b/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java index 0f5782f86..828b04a9d 100644 --- a/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java +++ b/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java @@ -62,7 +62,9 @@ public final class MinecraftProtocol { SUPPORTED_BEDROCK_CODECS.add(Bedrock_v486.V486_CODEC.toBuilder() .minecraftVersion("1.18.10/1.18.12") // 1.18.11 is also supported, but was only on Switch and since that auto-updates it's not needed .build()); - SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC); + SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC.toBuilder() + .minecraftVersion("1.18.30/1.18.31") + .build()); } /** From 8a1799e0e3d16177b5e358710b4e8ae0e3fab24e Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 29 Apr 2022 13:24:58 -0400 Subject: [PATCH 099/110] Remove block tag adding to villager trading Fixes sugar cane being untradeable. --- .../populator/BlockRegistryPopulator.java | 6 ------ .../geyser/registry/type/BlockMappings.java | 15 --------------- .../inventory/JavaMerchantOffersTranslator.java | 8 ++------ 3 files changed, 2 insertions(+), 27 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java index d8aa6a456..412d7d779 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java @@ -132,7 +132,6 @@ public class BlockRegistryPopulator { } catch (Exception e) { throw new AssertionError("Unable to get blocks from runtime block states", e); } - Map javaIdentifierToBedrockTag = new Object2ObjectOpenHashMap<>(blocksTag.size()); // New since 1.16.100 - find the block runtime ID by the order given to us in the block palette, // as we no longer send a block palette Object2IntMap blockStateOrderedMap = new Object2IntOpenHashMap<>(blocksTag.size()); @@ -202,10 +201,6 @@ public class BlockRegistryPopulator { flowerPotBlocks.put(cleanJavaIdentifier.intern(), blocksTag.get(bedrockRuntimeId)); } - if (!cleanJavaIdentifier.equals(entry.getValue().get("bedrock_identifier").asText())) { - javaIdentifierToBedrockTag.put(cleanJavaIdentifier.intern(), blocksTag.get(bedrockRuntimeId)); - } - javaToBedrockBlocks[javaRuntimeId] = bedrockRuntimeId; } @@ -240,7 +235,6 @@ public class BlockRegistryPopulator { BlockRegistries.BLOCKS.register(palette.getKey().valueInt(), builder.blockStateVersion(stateVersion) .javaToBedrockBlocks(javaToBedrockBlocks) - .javaIdentifierToBedrockTag(javaIdentifierToBedrockTag) .itemFrames(itemFrames) .flowerPotBlocks(flowerPotBlocks) .jigsawStateIds(jigsawStateIds) diff --git a/core/src/main/java/org/geysermc/geyser/registry/type/BlockMappings.java b/core/src/main/java/org/geysermc/geyser/registry/type/BlockMappings.java index a105682a6..41318ee64 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/type/BlockMappings.java +++ b/core/src/main/java/org/geysermc/geyser/registry/type/BlockMappings.java @@ -47,12 +47,6 @@ public class BlockMappings { NbtList bedrockBlockStates; - /** - * Contains a map of Java blocks to their respective Bedrock block tag, if the Java identifier is different from Bedrock. - * Required to fix villager trades with these blocks. - */ - Map javaIdentifierToBedrockTag; - int commandBlockRuntimeId; Object2IntMap itemFrames; @@ -74,13 +68,4 @@ public class BlockMappings { public boolean isItemFrame(int bedrockBlockRuntimeId) { return this.itemFrames.values().contains(bedrockBlockRuntimeId); } - - /** - * @param cleanJavaIdentifier the clean Java identifier of the block to look up - * - * @return the block tag of the block name mapped from Java to Bedrock. - */ - public NbtMap getBedrockBlockNbt(String cleanJavaIdentifier) { - return this.javaIdentifierToBedrockTag.get(cleanJavaIdentifier); - } } \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaMerchantOffersTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaMerchantOffersTranslator.java index 8af5c8af1..1c9ded0c1 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaMerchantOffersTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaMerchantOffersTranslator.java @@ -178,12 +178,8 @@ public class JavaMerchantOffersTranslator extends PacketTranslator Date: Sat, 30 Apr 2022 20:40:34 -0400 Subject: [PATCH 100/110] Fix lava and snow cauldrons looking wrong Fixes #2955 --- .../geyser/level/block/BlockStateValues.java | 18 +++++++++- .../block/entity/BedrockOnlyBlockEntity.java | 15 ++++++++ .../JavaLevelChunkWithLightTranslator.java | 22 ++++++------ .../geyser/util/BlockEntityUtils.java | 35 ++++++++----------- 4 files changed, 58 insertions(+), 32 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/level/block/BlockStateValues.java b/core/src/main/java/org/geysermc/geyser/level/block/BlockStateValues.java index 48d0e80e0..34569c5af 100644 --- a/core/src/main/java/org/geysermc/geyser/level/block/BlockStateValues.java +++ b/core/src/main/java/org/geysermc/geyser/level/block/BlockStateValues.java @@ -46,6 +46,7 @@ import java.util.Locale; public final class BlockStateValues { private static final Int2IntMap BANNER_COLORS = new FixedInt2IntMap(); private static final Int2ByteMap BED_COLORS = new FixedInt2ByteMap(); + private static final IntSet NON_WATER_CAULDRONS = new IntOpenHashSet(); private static final Int2ByteMap COMMAND_BLOCK_VALUES = new Int2ByteOpenHashMap(); private static final Int2ObjectMap DOUBLE_CHEST_VALUES = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap FLOWER_POT_VALUES = new Int2ObjectOpenHashMap<>(); @@ -62,6 +63,7 @@ public final class BlockStateValues { private static final Int2ByteMap SKULL_ROTATIONS = new Int2ByteOpenHashMap(); private static final Int2IntMap SKULL_WALL_DIRECTIONS = new Int2IntOpenHashMap(); private static final Int2ByteMap SHULKERBOX_DIRECTIONS = new FixedInt2ByteMap(); + private static final IntSet WATER_CAULDRONS = new IntOpenHashSet(); private static final Int2IntMap WATER_LEVEL = new Int2IntOpenHashMap(); public static final int JAVA_AIR_ID = 0; @@ -176,7 +178,7 @@ public final class BlockStateValues { return; } - if (javaId.startsWith("minecraft:water")) { + if (javaId.startsWith("minecraft:water") && !javaId.contains("cauldron")) { String strLevel = javaId.substring(javaId.lastIndexOf("level=") + 6, javaId.length() - 1); int level = Integer.parseInt(strLevel); WATER_LEVEL.put(javaBlockState, level); @@ -189,6 +191,11 @@ public final class BlockStateValues { if (direction.isHorizontal()) { HORIZONTAL_FACING_JIGSAWS.add(javaBlockState); } + return; + } + + if (javaId.contains("_cauldron") && !javaId.contains("water_")) { + NON_WATER_CAULDRONS.add(javaBlockState); } } @@ -214,6 +221,15 @@ public final class BlockStateValues { return BED_COLORS.getOrDefault(state, (byte) -1); } + /** + * Non-water cauldrons (since Bedrock 1.18.30) must have a block entity packet sent on chunk load to fix rendering issues. + * + * @return if this Java block state is a non-empty non-water cauldron + */ + public static boolean isCauldron(int state) { + return NON_WATER_CAULDRONS.contains(state); + } + /** * The block state in Java and Bedrock both contain the conditional bit, however command block block entity tags * in Bedrock need the conditional information. diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/BedrockOnlyBlockEntity.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/BedrockOnlyBlockEntity.java index 0ec7219c3..94760b66c 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/BedrockOnlyBlockEntity.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/BedrockOnlyBlockEntity.java @@ -26,7 +26,10 @@ package org.geysermc.geyser.translator.level.block.entity; import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.nbt.NbtList; import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtType; +import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.session.GeyserSession; /** @@ -59,6 +62,18 @@ public interface BedrockOnlyBlockEntity extends RequiresBlockState { return FlowerPotBlockEntityTranslator.getTag(session, blockState, position); } else if (PistonBlockEntityTranslator.isBlock(blockState)) { return PistonBlockEntityTranslator.getTag(blockState, position); + } else if (BlockStateValues.isCauldron(blockState)) { + // As of 1.18.30: this is required to make rendering not look weird on chunk load (lava and snow cauldrons look dim) + return NbtMap.builder() + .putString("id", "Cauldron") + .putByte("isMovable", (byte) 0) + .putShort("PotionId", (short) -1) + .putShort("PotionType", (short) -1) + .putList("Items", NbtType.END, NbtList.EMPTY) + .putInt("x", position.getX()) + .putInt("y", position.getY()) + .putInt("z", position.getZ()) + .build(); } return null; } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelChunkWithLightTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelChunkWithLightTranslator.java index 165d90b36..3855b1139 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelChunkWithLightTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelChunkWithLightTranslator.java @@ -99,7 +99,7 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator bedrockBlockEntities = new ObjectArrayList<>(blockEntities.length); BitSet waterloggedPaletteIds = new BitSet(); - BitSet pistonOrFlowerPaletteIds = new BitSet(); + BitSet bedrockOnlyBlockEntityIds = new BitSet(); BedrockDimension bedrockDimension = session.getChunkCache().getBedrockDimension(); int maxBedrockSectionY = (bedrockDimension.height() >> 4) - 1; @@ -144,7 +144,7 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator> 8) & 0xF), (packet.getZ() << 4) + ((yzx >> 4) & 0xF)), javaId @@ -173,7 +173,7 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator> 8) & 0xF), (packet.getZ() << 4) + ((yzx >> 4) & 0xF)), javaPalette.idToState(paletteId) @@ -233,9 +233,9 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator BEDROCK_ONLY_BLOCK_ENTITIES = new ObjectArrayList<>(); + public static final List BEDROCK_ONLY_BLOCK_ENTITIES = List.of( + (BedrockOnlyBlockEntity) Registries.BLOCK_ENTITIES.get().get(BlockEntityType.CHEST), + new FlowerPotBlockEntityTranslator() + ); /** * Contains a list of irregular block entity name translations that can't be fit into the regex */ - public static final Map BLOCK_ENTITY_TRANSLATIONS = new HashMap<>() { - { + public static final Map BLOCK_ENTITY_TRANSLATIONS = Map.of( // Bedrock/Java differences - put(BlockEntityType.ENCHANTING_TABLE, "EnchantTable"); - put(BlockEntityType.JIGSAW, "JigsawBlock"); - put(BlockEntityType.PISTON, "PistonArm"); - put(BlockEntityType.TRAPPED_CHEST, "Chest"); + BlockEntityType.ENCHANTING_TABLE, "EnchantTable", + BlockEntityType.JIGSAW, "JigsawBlock", + BlockEntityType.PISTON, "PistonArm", + BlockEntityType.TRAPPED_CHEST, "Chest" // There are some legacy IDs sent but as far as I can tell they are not needed for things to work properly - } - }; - - static { - // Seeing as there are only two - and, hopefully, will only ever be two - we can hardcode this - BEDROCK_ONLY_BLOCK_ENTITIES.add((BedrockOnlyBlockEntity) Registries.BLOCK_ENTITIES.get().get(BlockEntityType.CHEST)); - BEDROCK_ONLY_BLOCK_ENTITIES.add(new FlowerPotBlockEntityTranslator()); - } + ); public static String getBedrockBlockEntityId(BlockEntityType type) { // These are the only exceptions when it comes to block entity ids @@ -77,9 +72,9 @@ public class BlockEntityUtils { String id = type.name(); // Split at every space or capital letter - for the latter, some legacy Java block entity tags are the correct format already - String[] words = id.split("_");; + String[] words = id.split("_"); for (int i = 0; i < words.length; i++) { - words[i] = words[i].substring(0, 1).toUpperCase() + words[i].substring(1).toLowerCase(); + words[i] = words[i].substring(0, 1).toUpperCase(Locale.ROOT) + words[i].substring(1).toLowerCase(Locale.ROOT); } return String.join("", words); From 31a84ea302671904cd405ea82cffb277d99c5d0b Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 30 Apr 2022 20:42:29 -0400 Subject: [PATCH 101/110] Remove unused set --- .../java/org/geysermc/geyser/level/block/BlockStateValues.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/level/block/BlockStateValues.java b/core/src/main/java/org/geysermc/geyser/level/block/BlockStateValues.java index 34569c5af..a9b3ffedc 100644 --- a/core/src/main/java/org/geysermc/geyser/level/block/BlockStateValues.java +++ b/core/src/main/java/org/geysermc/geyser/level/block/BlockStateValues.java @@ -46,12 +46,12 @@ import java.util.Locale; public final class BlockStateValues { private static final Int2IntMap BANNER_COLORS = new FixedInt2IntMap(); private static final Int2ByteMap BED_COLORS = new FixedInt2ByteMap(); - private static final IntSet NON_WATER_CAULDRONS = new IntOpenHashSet(); private static final Int2ByteMap COMMAND_BLOCK_VALUES = new Int2ByteOpenHashMap(); private static final Int2ObjectMap DOUBLE_CHEST_VALUES = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap FLOWER_POT_VALUES = new Int2ObjectOpenHashMap<>(); private static final IntSet HORIZONTAL_FACING_JIGSAWS = new IntOpenHashSet(); private static final LecternHasBookMap LECTERN_BOOK_STATES = new LecternHasBookMap(); + private static final IntSet NON_WATER_CAULDRONS = new IntOpenHashSet(); private static final Int2IntMap NOTEBLOCK_PITCHES = new FixedInt2IntMap(); private static final Int2BooleanMap PISTON_VALUES = new Int2BooleanOpenHashMap(); private static final IntSet STICKY_PISTONS = new IntOpenHashSet(); @@ -63,7 +63,6 @@ public final class BlockStateValues { private static final Int2ByteMap SKULL_ROTATIONS = new Int2ByteOpenHashMap(); private static final Int2IntMap SKULL_WALL_DIRECTIONS = new Int2IntOpenHashMap(); private static final Int2ByteMap SHULKERBOX_DIRECTIONS = new FixedInt2ByteMap(); - private static final IntSet WATER_CAULDRONS = new IntOpenHashSet(); private static final Int2IntMap WATER_LEVEL = new Int2IntOpenHashMap(); public static final int JAVA_AIR_ID = 0; From 05e98c3a103c5a28f6b29ac1e86c7d4f7cc7e632 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 4 May 2022 15:54:19 -0400 Subject: [PATCH 102/110] Fix edge case in health code since 1.18.30 Fixes #2957 --- .../entity/type/player/SessionPlayerEntity.java | 4 ++++ .../java/entity/player/JavaSetHealthTranslator.java | 13 +++++++++++++ 2 files changed, 17 insertions(+) diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java index ae8d23810..6edcd60f3 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java @@ -134,6 +134,10 @@ public class SessionPlayerEntity extends PlayerEntity { return maxHealth; } + public float getHealth() { + return this.health; + } + public void setHealth(float health) { this.health = health; } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaSetHealthTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaSetHealthTranslator.java index d989fe964..67047d00e 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaSetHealthTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaSetHealthTranslator.java @@ -27,6 +27,7 @@ package org.geysermc.geyser.translator.protocol.java.entity.player; import com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.player.ClientboundSetHealthPacket; import com.nukkitx.protocol.bedrock.data.AttributeData; +import com.nukkitx.protocol.bedrock.packet.RespawnPacket; import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket; import org.geysermc.geyser.entity.attribute.GeyserAttributeType; import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; @@ -43,6 +44,18 @@ public class JavaSetHealthTranslator extends PacketTranslator 0f) { + // Needed as of 1.18.30 (tested with a totem of undying on SPIGOT 1.12.2 + // This shouldn't be triggered on a proper respawn because JavaSetHealthTranslator sets the health back to 20 + // https://github.com/GeyserMC/Geyser/issues/2957 + RespawnPacket respawnPacket = new RespawnPacket(); + respawnPacket.setRuntimeEntityId(0); + respawnPacket.setPosition(entity.getPosition()); + respawnPacket.setState(RespawnPacket.State.SERVER_READY); + session.sendUpstreamPacket(respawnPacket); + } + entity.setHealth(packet.getHealth()); UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket(); From f38c1fbc0f663f82e0374198e42a7487cbb8eb5c Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 6 May 2022 15:32:43 -0400 Subject: [PATCH 103/110] Spigot: programmatically add Geyser permissions and fix reloading --- .../platform/spigot/GeyserSpigotPlugin.java | 37 +++++++++++++++++-- .../spigot/src/main/resources/plugin.yml | 31 ---------------- .../geyser/command/GeyserCommand.java | 9 +++++ .../geyser/command/defaults/DumpCommand.java | 5 +++ .../geyser/command/defaults/ListCommand.java | 5 +++ .../command/defaults/ReloadCommand.java | 5 +++ .../geyser/command/defaults/StopCommand.java | 5 +++ .../command/defaults/VersionCommand.java | 5 +++ 8 files changed, 67 insertions(+), 35 deletions(-) diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java index 6e490bfca..4ece501c4 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java @@ -32,6 +32,8 @@ import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; import me.lucko.commodore.CommodoreProvider; import org.bukkit.Bukkit; import org.bukkit.command.PluginCommand; +import org.bukkit.permissions.Permission; +import org.bukkit.permissions.PermissionDefault; import org.bukkit.plugin.java.JavaPlugin; import org.geysermc.common.PlatformType; import org.geysermc.geyser.Constants; @@ -39,6 +41,7 @@ import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.adapters.spigot.SpigotAdapters; import org.geysermc.geyser.command.CommandManager; +import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.dump.BootstrapDumpInfo; import org.geysermc.geyser.level.WorldManager; @@ -61,10 +64,16 @@ import java.io.IOException; import java.net.SocketAddress; import java.nio.file.Path; import java.util.List; +import java.util.Map; import java.util.UUID; import java.util.logging.Level; public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { + /** + * Determines if the plugin has been ran once before, including before /geyser reload. + */ + private static boolean INITIALIZED = false; + private GeyserSpigotCommandManager geyserCommandManager; private GeyserSpigotConfiguration geyserConfig; private GeyserSpigotInjector geyserInjector; @@ -232,14 +241,32 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { } geyserLogger.debug("Using default world manager: " + this.geyserWorldManager.getClass()); } - GeyserSpigotBlockPlaceListener blockPlaceListener = new GeyserSpigotBlockPlaceListener(geyser, this.geyserWorldManager); - Bukkit.getServer().getPluginManager().registerEvents(blockPlaceListener, this); - - Bukkit.getServer().getPluginManager().registerEvents(new GeyserPistonListener(geyser, this.geyserWorldManager), this); PluginCommand pluginCommand = this.getCommand("geyser"); pluginCommand.setExecutor(new GeyserSpigotCommandExecutor(geyser)); + if (!INITIALIZED) { + // Register permissions so they appear in, for example, LuckPerms' UI + // Re-registering permissions throws an error + for (Map.Entry entry : geyserCommandManager.getCommands().entrySet()) { + GeyserCommand command = entry.getValue(); + if (command.getAliases().contains(entry.getKey())) { + // Don't register aliases + continue; + } + + Bukkit.getPluginManager().addPermission(new Permission(command.getPermission(), + GeyserLocale.getLocaleStringLog(command.getDescription()), + command.isSuggestedOpOnly() ? PermissionDefault.OP : PermissionDefault.TRUE)); + } + + // Events cannot be unregistered - re-registering results in duplicate firings + GeyserSpigotBlockPlaceListener blockPlaceListener = new GeyserSpigotBlockPlaceListener(geyser, this.geyserWorldManager); + Bukkit.getServer().getPluginManager().registerEvents(blockPlaceListener, this); + + Bukkit.getServer().getPluginManager().registerEvents(new GeyserPistonListener(geyser, this.geyserWorldManager), this); + } + boolean brigadierSupported = CommodoreProvider.isSupported(); geyserLogger.debug("Brigadier supported? " + brigadierSupported); if (brigadierSupported) { @@ -248,6 +275,8 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { // Check to ensure the current setup can support the protocol version Geyser uses GeyserSpigotVersionChecker.checkForSupportedProtocol(geyserLogger, isViaVersion); + + INITIALIZED = true; } @Override diff --git a/bootstrap/spigot/src/main/resources/plugin.yml b/bootstrap/spigot/src/main/resources/plugin.yml index 18773402e..aa2747979 100644 --- a/bootstrap/spigot/src/main/resources/plugin.yml +++ b/bootstrap/spigot/src/main/resources/plugin.yml @@ -9,34 +9,3 @@ commands: geyser: description: The main command for Geyser. usage: /geyser -permissions: - geyser.command.help: - description: Shows help for all registered commands. - default: true - geyser.command.offhand: - description: Puts an items in your offhand. - default: true - geyser.command.advancements: - description: Shows the advancements of the player on the server. - default: true - geyser.command.tooltips: - description: Toggles showing advanced tooltips on your items. - default: true - geyser.command.statistics: - description: Shows the statistics of the player on the server. - default: true - geyser.command.settings: - description: Modify user settings - default: true - geyser.command.list: - description: List all players connected through Geyser. - default: op - geyser.command.dump: - description: Dumps Geyser debug information for bug reports. - default: op - geyser.command.reload: - description: Reloads the Geyser configurations. Kicks all players when used! - default: false - geyser.command.version: - description: Shows the current Geyser version and checks for updates. - default: op diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java index 20451b5e8..a22c69c04 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java @@ -86,4 +86,13 @@ public abstract class GeyserCommand { public boolean isBedrockOnly() { return false; } + + /** + * Used for permission defaults on server implementations. + * + * @return if this command is designated to be used only by server operators. + */ + public boolean isSuggestedOpOnly() { + return false; + } } \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java index bd98d2b31..0bac381ba 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java @@ -145,4 +145,9 @@ public class DumpCommand extends GeyserCommand { public List getSubCommands() { return Arrays.asList("offline", "full", "logs"); } + + @Override + public boolean isSuggestedOpOnly() { + return true; + } } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/ListCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/ListCommand.java index f1004c3fb..0a4cfa023 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/ListCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/ListCommand.java @@ -51,4 +51,9 @@ public class ListCommand extends GeyserCommand { sender.sendMessage(message); } + + @Override + public boolean isSuggestedOpOnly() { + return true; + } } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/ReloadCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/ReloadCommand.java index b6a728382..e970e5d3d 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/ReloadCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/ReloadCommand.java @@ -54,4 +54,9 @@ public class ReloadCommand extends GeyserCommand { geyser.getSessionManager().disconnectAll("geyser.commands.reload.kick"); geyser.reload(); } + + @Override + public boolean isSuggestedOpOnly() { + return true; + } } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/StopCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/StopCommand.java index 903e3bf4b..9c7bd8140 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/StopCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/StopCommand.java @@ -54,4 +54,9 @@ public class StopCommand extends GeyserCommand { geyser.getBootstrap().onDisable(); } + + @Override + public boolean isSuggestedOpOnly() { + return true; + } } \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java index 6ec816b12..f4f62892a 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java @@ -100,4 +100,9 @@ public class VersionCommand extends GeyserCommand { } } } + + @Override + public boolean isSuggestedOpOnly() { + return true; + } } From db13b4c276f7be829af85ba66e0dc07486696247 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 10 May 2022 12:40:15 -0400 Subject: [PATCH 104/110] Fix decoding for some UTF-8 characters --- core/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/pom.xml b/core/pom.xml index adc841ebe..8af2aa907 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -121,7 +121,7 @@ com.github.CloudburstMC.Protocol bedrock-v503 - 2a344e4 + 297567d compile From b33cc512b480901c205aa4b1f4d216ea5156a22a Mon Sep 17 00:00:00 2001 From: David Choo Date: Sat, 14 May 2022 15:12:18 -0400 Subject: [PATCH 105/110] Add custom skull render distance (#2751) * Add player skull render distance * Improve updateVisibleSkulls a bit Avoid rechecking visibility on small movements * Periodically despawn unused skull entities * Don't hide skull entity for position/rotation changes Prevents flickering for skulls that are rotating * Update visible skulls when a skull is removed * Only update on removal if an entity is assigned * No need to check for skull in ChunkUtils Update copyright year * Avoid rechecking all skulls when a skull is added/removed * Allow skull render distance and number to be configured Renamed some fields to better match their values * Compare texture property directly from GameProfile * Remove unnecessary blockState field from SkullPlayerEntity * Use binarySearch for insertion Wait for player movement before loading skulls * Allow culling to be disabled by setting max-visible-custom-skulls to -1 * Only remove skulls in inRangeSkulls when culling is enabled * Add suggestions from review * Merge the for loops in updateVisibleSkulls * Fix skulls being leaked on chunk unload --- .../configuration/GeyserConfiguration.java | 4 + .../GeyserJacksonConfiguration.java | 6 + .../entity/type/player/SkullPlayerEntity.java | 72 ++++-- .../geyser/session/GeyserSession.java | 3 +- .../geyser/session/cache/SkullCache.java | 211 ++++++++++++++++++ .../entity/SkullBlockEntityTranslator.java | 58 +---- .../player/BedrockMovePlayerTranslator.java | 2 + .../level/JavaBlockEntityDataTranslator.java | 2 +- .../level/JavaForgetLevelChunkTranslator.java | 15 +- .../JavaLevelChunkWithLightTranslator.java | 2 +- .../org/geysermc/geyser/util/ChunkUtils.java | 6 +- core/src/main/resources/config.yml | 7 + 12 files changed, 306 insertions(+), 82 deletions(-) create mode 100644 core/src/main/java/org/geysermc/geyser/session/cache/SkullCache.java diff --git a/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java b/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java index 7bb73a648..1f188cf40 100644 --- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java @@ -101,6 +101,10 @@ public interface GeyserConfiguration { boolean isAllowCustomSkulls(); + int getMaxVisibleCustomSkulls(); + + int getCustomSkullRenderDistance(); + IMetricsInfo getMetrics(); int getPendingAuthenticationTimeout(); diff --git a/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java b/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java index 03a3617e3..30a947e53 100644 --- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java @@ -130,6 +130,12 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration @JsonProperty("allow-custom-skulls") private boolean allowCustomSkulls = true; + @JsonProperty("max-visible-custom-skulls") + private int maxVisibleCustomSkulls = 128; + + @JsonProperty("custom-skull-render-distance") + private int customSkullRenderDistance = 32; + @JsonProperty("add-non-bedrock-items") private boolean addNonBedrockItems = true; diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/SkullPlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/SkullPlayerEntity.java index f1a447b57..6c15a4d3e 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/SkullPlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/SkullPlayerEntity.java @@ -26,33 +26,28 @@ package org.geysermc.geyser.entity.type.player; import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.GameType; import com.nukkitx.protocol.bedrock.data.PlayerPermission; import com.nukkitx.protocol.bedrock.data.command.CommandPermission; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.packet.AddPlayerPacket; -import lombok.Getter; +import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.session.cache.SkullCache; +import org.geysermc.geyser.skin.SkullSkinManager; import java.util.UUID; +import java.util.concurrent.TimeUnit; /** * A wrapper to handle skulls more effectively - skulls have to be treated as entities since there are no * custom player skulls in Bedrock. */ public class SkullPlayerEntity extends PlayerEntity { - /** - * Stores the block state that the skull is associated with. Used to determine if the block in the skull's position - * has changed - */ - @Getter - private final int blockState; - public SkullPlayerEntity(GeyserSession session, long geyserId, Vector3f position, float rotation, int blockState, String texturesProperty) { - super(session, 0, geyserId, UUID.randomUUID(), position, Vector3f.ZERO, rotation, 0, rotation, "", texturesProperty); - this.blockState = blockState; + public SkullPlayerEntity(GeyserSession session, long geyserId) { + super(session, 0, geyserId, UUID.randomUUID(), Vector3f.ZERO, Vector3f.ZERO, 0, 0, 0, "", null); setPlayerList(false); } @@ -95,8 +90,57 @@ public class SkullPlayerEntity extends PlayerEntity { session.sendUpstreamPacket(addPlayerPacket); } - public void despawnEntity(Vector3i position) { - this.despawnEntity(); - session.getSkullCache().remove(position, this); + /** + * Hide the player entity so that it can be reused for a different skull. + */ + public void free() { + setFlag(EntityFlag.INVISIBLE, true); + updateBedrockMetadata(); + + // Move skull entity out of the way + moveAbsolute(session.getPlayerEntity().getPosition().up(128), 0, 0, 0, false, true); + } + + public void updateSkull(SkullCache.Skull skull) { + if (!skull.getTexturesProperty().equals(getTexturesProperty())) { + // Make skull invisible as we change skins + setFlag(EntityFlag.INVISIBLE, true); + updateBedrockMetadata(); + + setTexturesProperty(skull.getTexturesProperty()); + + SkullSkinManager.requestAndHandleSkin(this, session, (skin -> session.scheduleInEventLoop(() -> { + // Delay to minimize split-second "player" pop-in + setFlag(EntityFlag.INVISIBLE, false); + updateBedrockMetadata(); + }, 250, TimeUnit.MILLISECONDS))); + } else { + // Just a rotation/position change + setFlag(EntityFlag.INVISIBLE, false); + updateBedrockMetadata(); + } + + float x = skull.getPosition().getX() + .5f; + float y = skull.getPosition().getY() - .01f; + float z = skull.getPosition().getZ() + .5f; + float rotation; + + int blockState = skull.getBlockState(); + byte floorRotation = BlockStateValues.getSkullRotation(blockState); + if (floorRotation == -1) { + // Wall skull + y += 0.25f; + rotation = BlockStateValues.getSkullWallDirections().get(blockState); + switch ((int) rotation) { + case 180 -> z += 0.24f; // North + case 0 -> z -= 0.24f; // South + case 90 -> x += 0.24f; // West + case 270 -> x -= 0.24f; // East + } + } else { + rotation = (180f + (floorRotation * 22.5f)) % 360; + } + + moveAbsolute(Vector3f.from(x, y, z), rotation, 0, rotation, true, true); } } diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index 72eaaf0f7..5f264329e 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -173,6 +173,7 @@ public class GeyserSession implements GeyserConnection, CommandSender { private final LodestoneCache lodestoneCache; private final PistonCache pistonCache; private final PreferencesCache preferencesCache; + private final SkullCache skullCache; private final TagCache tagCache; private final WorldCache worldCache; @@ -220,7 +221,6 @@ public class GeyserSession implements GeyserConnection, CommandSender { @Setter private ItemMappings itemMappings; - private final Map skullCache = new Object2ObjectOpenHashMap<>(); private final Long2ObjectMap storedMaps = new Long2ObjectOpenHashMap<>(); /** @@ -530,6 +530,7 @@ public class GeyserSession implements GeyserConnection, CommandSender { this.lodestoneCache = new LodestoneCache(); this.pistonCache = new PistonCache(this); this.preferencesCache = new PreferencesCache(this); + this.skullCache = new SkullCache(this); this.tagCache = new TagCache(); this.worldCache = new WorldCache(this); diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/SkullCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/SkullCache.java new file mode 100644 index 000000000..f26e1cce3 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/session/cache/SkullCache.java @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2019-2022 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.session.cache; + +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.math.vector.Vector3i; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import lombok.Data; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.geysermc.geyser.entity.type.player.SkullPlayerEntity; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.*; + +public class SkullCache { + private final int maxVisibleSkulls; + private final boolean cullingEnabled; + + private final int skullRenderDistanceSquared; + + /** + * The time in milliseconds before unused skull entities are despawned + */ + private static final long CLEANUP_PERIOD = 10000; + + @Getter + private final Map skulls = new Object2ObjectOpenHashMap<>(); + + private final List inRangeSkulls = new ArrayList<>(); + + private final Deque unusedSkullEntities = new ArrayDeque<>(); + private int totalSkullEntities = 0; + + private final GeyserSession session; + + private Vector3f lastPlayerPosition; + + private long lastCleanup = System.currentTimeMillis(); + + public SkullCache(GeyserSession session) { + this.session = session; + this.maxVisibleSkulls = session.getGeyser().getConfig().getMaxVisibleCustomSkulls(); + this.cullingEnabled = this.maxVisibleSkulls != -1; + + // Normal skulls are not rendered beyond 64 blocks + int distance = Math.min(session.getGeyser().getConfig().getCustomSkullRenderDistance(), 64); + this.skullRenderDistanceSquared = distance * distance; + } + + public void putSkull(Vector3i position, String texturesProperty, int blockState) { + Skull skull = skulls.computeIfAbsent(position, Skull::new); + skull.texturesProperty = texturesProperty; + skull.blockState = blockState; + + if (skull.entity != null) { + skull.entity.updateSkull(skull); + } else { + if (!cullingEnabled) { + assignSkullEntity(skull); + return; + } + if (lastPlayerPosition == null) { + return; + } + skull.distanceSquared = position.distanceSquared(lastPlayerPosition.getX(), lastPlayerPosition.getY(), lastPlayerPosition.getZ()); + if (skull.distanceSquared < skullRenderDistanceSquared) { + // Keep list in order + int i = Collections.binarySearch(inRangeSkulls, skull, Comparator.comparingInt(Skull::getDistanceSquared)); + if (i < 0) { // skull.distanceSquared is a new distance value + i = -i - 1; + } + inRangeSkulls.add(i, skull); + + if (i < maxVisibleSkulls) { + // Reassign entity from the farthest skull to this one + if (inRangeSkulls.size() > maxVisibleSkulls) { + freeSkullEntity(inRangeSkulls.get(maxVisibleSkulls)); + } + assignSkullEntity(skull); + } + } + } + } + + public void removeSkull(Vector3i position) { + Skull skull = skulls.remove(position); + if (skull != null) { + boolean hadEntity = skull.entity != null; + freeSkullEntity(skull); + + if (cullingEnabled) { + inRangeSkulls.remove(skull); + if (hadEntity && inRangeSkulls.size() >= maxVisibleSkulls) { + // Reassign entity to the closest skull without an entity + assignSkullEntity(inRangeSkulls.get(maxVisibleSkulls - 1)); + } + } + } + } + + public void updateVisibleSkulls() { + if (cullingEnabled) { + // No need to recheck skull visibility for small movements + if (lastPlayerPosition != null && session.getPlayerEntity().getPosition().distanceSquared(lastPlayerPosition) < 4) { + return; + } + lastPlayerPosition = session.getPlayerEntity().getPosition(); + + inRangeSkulls.clear(); + for (Skull skull : skulls.values()) { + skull.distanceSquared = skull.position.distanceSquared(lastPlayerPosition.getX(), lastPlayerPosition.getY(), lastPlayerPosition.getZ()); + if (skull.distanceSquared > skullRenderDistanceSquared) { + freeSkullEntity(skull); + } else { + inRangeSkulls.add(skull); + } + } + inRangeSkulls.sort(Comparator.comparingInt(Skull::getDistanceSquared)); + + for (int i = inRangeSkulls.size() - 1; i >= 0; i--) { + if (i < maxVisibleSkulls) { + assignSkullEntity(inRangeSkulls.get(i)); + } else { + freeSkullEntity(inRangeSkulls.get(i)); + } + } + } + + // Occasionally clean up unused entities as we want to keep skull + // entities around for later use, to reduce "player" pop-in + if ((System.currentTimeMillis() - lastCleanup) > CLEANUP_PERIOD) { + lastCleanup = System.currentTimeMillis(); + for (SkullPlayerEntity entity : unusedSkullEntities) { + entity.despawnEntity(); + totalSkullEntities--; + } + unusedSkullEntities.clear(); + } + } + + private void assignSkullEntity(Skull skull) { + if (skull.entity != null) { + return; + } + if (unusedSkullEntities.isEmpty()) { + if (!cullingEnabled || totalSkullEntities < maxVisibleSkulls) { + // Create a new entity + long geyserId = session.getEntityCache().getNextEntityId().incrementAndGet(); + skull.entity = new SkullPlayerEntity(session, geyserId); + skull.entity.spawnEntity(); + skull.entity.updateSkull(skull); + totalSkullEntities++; + } + } else { + // Reuse an entity + skull.entity = unusedSkullEntities.removeFirst(); + skull.entity.updateSkull(skull); + } + } + + private void freeSkullEntity(Skull skull) { + if (skull.entity != null) { + skull.entity.free(); + unusedSkullEntities.addFirst(skull.entity); + skull.entity = null; + } + } + + public void clear() { + skulls.clear(); + inRangeSkulls.clear(); + unusedSkullEntities.clear(); + totalSkullEntities = 0; + lastPlayerPosition = null; + } + + @RequiredArgsConstructor + @Data + public static class Skull { + private String texturesProperty; + private int blockState; + private SkullPlayerEntity entity; + + private final Vector3i position; + private int distanceSquared; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SkullBlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SkullBlockEntityTranslator.java index 50d79c10f..94e2d4767 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SkullBlockEntityTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SkullBlockEntityTranslator.java @@ -29,19 +29,14 @@ import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.ListTag; import com.github.steveice10.opennbt.tag.builtin.StringTag; -import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.nbt.NbtMapBuilder; -import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import org.geysermc.geyser.entity.type.player.SkullPlayerEntity; import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.skin.SkinProvider; -import org.geysermc.geyser.skin.SkullSkinManager; import java.util.LinkedHashMap; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; @BlockEntity(type = BlockEntityType.SKULL) public class SkullBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { @@ -74,65 +69,18 @@ public class SkullBlockEntityTranslator extends BlockEntityTranslator implements return CompletableFuture.completedFuture(null); } - public static void spawnPlayer(GeyserSession session, CompoundTag tag, int posX, int posY, int posZ, int blockState) { - float x = posX + .5f; - float y = posY - .01f; - float z = posZ + .5f; - float rotation; - - byte floorRotation = BlockStateValues.getSkullRotation(blockState); - if (floorRotation == -1) { - // Wall skull - y += 0.25f; - rotation = BlockStateValues.getSkullWallDirections().get(blockState); - switch ((int) rotation) { - case 180 -> z += 0.24f; // North - case 0 -> z -= 0.24f; // South - case 90 -> x += 0.24f; // West - case 270 -> x -= 0.24f; // East - } - } else { - rotation = (180f + (floorRotation * 22.5f)) % 360; - } - + public static void translateSkull(GeyserSession session, CompoundTag tag, int posX, int posY, int posZ, int blockState) { Vector3i blockPosition = Vector3i.from(posX, posY, posZ); - Vector3f entityPosition = Vector3f.from(x, y, z); - getTextures(tag).whenComplete((texturesProperty, throwable) -> { if (texturesProperty == null) { session.getGeyser().getLogger().debug("Custom skull with invalid SkullOwner tag: " + blockPosition + " " + tag); return; } - if (session.getEventLoop().inEventLoop()) { - spawnPlayer(session, texturesProperty, blockPosition, entityPosition, rotation, blockState); + session.getSkullCache().putSkull(blockPosition, texturesProperty, blockState); } else { - session.executeInEventLoop(() -> spawnPlayer(session, texturesProperty, blockPosition, entityPosition, rotation, blockState)); + session.executeInEventLoop(() -> session.getSkullCache().putSkull(blockPosition, texturesProperty, blockState)); } }); } - - private static void spawnPlayer(GeyserSession session, String texturesProperty, Vector3i blockPosition, - Vector3f entityPosition, float rotation, int blockState) { - long geyserId = session.getEntityCache().getNextEntityId().incrementAndGet(); - - SkullPlayerEntity existingSkull = session.getSkullCache().get(blockPosition); - if (existingSkull != null) { - // Ensure that two skulls can't spawn on the same point - existingSkull.despawnEntity(blockPosition); - } - - SkullPlayerEntity player = new SkullPlayerEntity(session, geyserId, entityPosition, rotation, blockState, texturesProperty); - - // Cache entity - session.getSkullCache().put(blockPosition, player); - - player.spawnEntity(); - - SkullSkinManager.requestAndHandleSkin(player, session, (skin -> session.scheduleInEventLoop(() -> { - // Delay to minimize split-second "player" pop-in - player.setFlag(EntityFlag.INVISIBLE, false); - player.updateBedrockMetadata(); - }, 250, TimeUnit.MILLISECONDS))); - } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockMovePlayerTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockMovePlayerTranslator.java index 8732b7909..0d3ef4cbc 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockMovePlayerTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockMovePlayerTranslator.java @@ -140,6 +140,8 @@ public class BedrockMovePlayerTranslator extends PacketTranslator { @@ -41,19 +43,18 @@ public class JavaForgetLevelChunkTranslator extends PacketTranslator iterator = session.getSkullCache().keySet().iterator(); - while (iterator.hasNext()) { - Vector3i position = iterator.next(); + // Checks if a skull is in an unloaded chunk then removes it + List removedSkulls = new ArrayList<>(); + for (Vector3i position : session.getSkullCache().getSkulls().keySet()) { if ((position.getX() >> 4) == packet.getX() && (position.getZ() >> 4) == packet.getZ()) { - session.getSkullCache().get(position).despawnEntity(); - iterator.remove(); + removedSkulls.add(position); } } + removedSkulls.forEach(session.getSkullCache()::removeSkull); if (!session.getGeyser().getWorldManager().shouldExpectLecternHandled()) { // Do the same thing with lecterns - iterator = session.getLecternCache().iterator(); + Iterator iterator = session.getLecternCache().iterator(); while (iterator.hasNext()) { Vector3i position = iterator.next(); if ((position.getX() >> 4) == packet.getX() && (position.getZ() >> 4) == packet.getZ()) { diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelChunkWithLightTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelChunkWithLightTranslator.java index 3855b1139..47ba98274 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelChunkWithLightTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelChunkWithLightTranslator.java @@ -275,7 +275,7 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator Date: Sun, 15 May 2022 13:52:18 -0400 Subject: [PATCH 106/110] Always show the world border at least five blocks away Previously, no indication that the world border exists would show if warning blocks was set to 0. --- .../geyser/session/GeyserSession.java | 14 ++++++++----- .../geyser/session/cache/WorldBorder.java | 20 +++++++++++++++---- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index 5f264329e..0daa43da8 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -69,7 +69,10 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.packet.*; import io.netty.channel.Channel; import io.netty.channel.EventLoop; -import it.unimi.dsi.fastutil.ints.*; +import it.unimi.dsi.fastutil.ints.Int2IntMap; +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; @@ -94,7 +97,6 @@ import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.ItemFrameEntity; import org.geysermc.geyser.entity.type.Tickable; import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; -import org.geysermc.geyser.entity.type.player.SkullPlayerEntity; import org.geysermc.geyser.inventory.Inventory; import org.geysermc.geyser.inventory.PlayerInventory; import org.geysermc.geyser.inventory.recipe.GeyserRecipe; @@ -1092,15 +1094,17 @@ public class GeyserSession implements GeyserConnection, CommandSender { worldBorder.resize(); } - if (!worldBorder.isWithinWarningBoundaries()) { + boolean shouldShowFog = !worldBorder.isWithinWarningBoundaries(); + if (shouldShowFog || worldBorder.isCloseToBorderBoundaries()) { // Show particles representing where the world border is worldBorder.drawWall(); // Set the mood - if (!isInWorldBorderWarningArea) { + if (shouldShowFog && !isInWorldBorderWarningArea) { isInWorldBorderWarningArea = true; sendFog("minecraft:fog_crimson_forest"); } - } else if (isInWorldBorderWarningArea) { + } + if (!shouldShowFog && isInWorldBorderWarningArea) { // Clear fog as we are outside the world border now removeFog("minecraft:fog_crimson_forest"); isInWorldBorderWarningArea = false; diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/WorldBorder.java b/core/src/main/java/org/geysermc/geyser/session/cache/WorldBorder.java index 66922ff0b..09e7e9234 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/WorldBorder.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/WorldBorder.java @@ -139,6 +139,18 @@ public class WorldBorder { return position.getX() > minX && position.getX() < maxX && position.getZ() > minZ && position.getZ() < maxZ; } + private static final int CLOSE_TO_BORDER = 5; + + /** + * @return if the player is close to the border boundaries. Used to always indicate a border even if there is no + * warning blocks set. + */ + public boolean isCloseToBorderBoundaries() { + Vector3f position = session.getPlayerEntity().getPosition(); + return !(position.getX() > minX + CLOSE_TO_BORDER && position.getX() < maxX - CLOSE_TO_BORDER + && position.getZ() > minZ + CLOSE_TO_BORDER && position.getZ() < maxZ - CLOSE_TO_BORDER); + } + /** * Confirms that the entity is within world border boundaries when they move. * Otherwise, if {@code adjustPosition} is true, this function will push the player back. @@ -246,16 +258,16 @@ public class WorldBorder { float particlePosY = entityPosition.getY(); float particlePosZ = entityPosition.getZ(); - if (entityPosition.getX() > warningMaxX) { + if (entityPosition.getX() > Math.min(warningMaxX, maxX - CLOSE_TO_BORDER)) { drawWall(Vector3f.from(maxX, particlePosY, particlePosZ), true); } - if (entityPosition.getX() < warningMinX) { + if (entityPosition.getX() < Math.max(warningMinX, minX + CLOSE_TO_BORDER)) { drawWall(Vector3f.from(minX, particlePosY, particlePosZ), true); } - if (entityPosition.getZ() > warningMaxZ) { + if (entityPosition.getZ() > Math.min(warningMaxZ, maxZ - CLOSE_TO_BORDER)) { drawWall(Vector3f.from(particlePosX, particlePosY, maxZ), false); } - if (entityPosition.getZ() < warningMinZ) { + if (entityPosition.getZ() < Math.max(warningMinZ, minZ + CLOSE_TO_BORDER)) { drawWall(Vector3f.from(particlePosX, particlePosY, minZ), false); } } From 8c9d1fe09f7d7eff90b12619df140177e980650c Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 15 May 2022 14:23:52 -0400 Subject: [PATCH 107/110] Allow language file overrides By placing a locale file in `languages/ll_CC.properties`, any strings in that file will take priority over Geyser's own. --- .gitignore | 3 +- .../geysermc/geyser/text/GeyserLocale.java | 43 ++++++++++++++++--- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 401002e1d..f1baa3abb 100644 --- a/.gitignore +++ b/.gitignore @@ -244,4 +244,5 @@ locales/ /cache/ /packs/ /dump.json -/saved-refresh-tokens.json \ No newline at end of file +/saved-refresh-tokens.json +/languages/ \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/text/GeyserLocale.java b/core/src/main/java/org/geysermc/geyser/text/GeyserLocale.java index da6ea4dc0..86e015c0f 100644 --- a/core/src/main/java/org/geysermc/geyser/text/GeyserLocale.java +++ b/core/src/main/java/org/geysermc/geyser/text/GeyserLocale.java @@ -28,10 +28,10 @@ package org.geysermc.geyser.text; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; +import java.io.*; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; import java.text.MessageFormat; import java.util.HashMap; import java.util.Locale; @@ -116,12 +116,22 @@ public class GeyserLocale { return locale; } + Properties localeProp = new Properties(); + + File localLanguage; + Path localFolder = bootstrap.getConfigFolder().resolve("languages"); + if (Files.exists(localFolder)) { + localLanguage = localFolder.resolve(locale + ".properties").toFile(); + } else { + localLanguage = null; + } + boolean validLocalLanguage = localLanguage != null && localLanguage.exists(); + InputStream localeStream = bootstrap.getResourceOrNull("languages/texts/" + locale + ".properties"); // Load the locale if (localeStream != null) { try { - Properties localeProp = new Properties(); try (InputStreamReader reader = new InputStreamReader(localeStream, StandardCharsets.UTF_8)) { localeProp.load(reader); } catch (Exception e) { @@ -130,18 +140,37 @@ public class GeyserLocale { // Insert the locale into the mappings LOCALE_MAPPINGS.put(locale, localeProp); - return locale; } finally { try { localeStream.close(); } catch (IOException ignored) {} } } else { - if (GeyserImpl.getInstance() != null) { + if (GeyserImpl.getInstance() != null && !validLocalLanguage) { + // Don't warn on missing locales if a local file has been found GeyserImpl.getInstance().getLogger().warning("Missing locale: " + locale); } - return null; } + + // Load any language overrides that exist after, to override any strings that we just added + // By loading both, we ensure that if a language string doesn't exist in the custom properties folder, + // it's loaded from our jar + if (validLocalLanguage) { + try (InputStream stream = new FileInputStream(localLanguage)) { + localeProp.load(stream); + } catch (IOException e) { + String message = "Unable to load custom language override!"; + if (GeyserImpl.getInstance() != null) { + GeyserImpl.getInstance().getLogger().error(message, e); + } else { + System.err.println(message); + e.printStackTrace(); + } + } + + LOCALE_MAPPINGS.putIfAbsent(locale, localeProp); + } + return localeProp.isEmpty() ? null : locale; } /** From c3c8161a43b8c4ce77a535e14a73060904ebb289 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 21 May 2022 16:28:36 +0100 Subject: [PATCH 108/110] Bump gson from 2.8.6 to 2.8.9 in /common (#2982) Bumps [gson](https://github.com/google/gson) from 2.8.6 to 2.8.9. - [Release notes](https://github.com/google/gson/releases) - [Changelog](https://github.com/google/gson/blob/master/CHANGELOG.md) - [Commits](https://github.com/google/gson/compare/gson-parent-2.8.6...gson-parent-2.8.9) --- updated-dependencies: - dependency-name: com.google.code.gson:gson dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- common/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/pom.xml b/common/pom.xml index b3903f412..0786f3f4d 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -25,7 +25,7 @@ com.google.code.gson gson - 2.8.6 + 2.8.9 \ No newline at end of file From 38625312a167c1c419166a3327f771f84cb74fca Mon Sep 17 00:00:00 2001 From: David Choo Date: Sat, 21 May 2022 11:54:32 -0400 Subject: [PATCH 109/110] Prevent max health from being set below 0 (#2980) * Prevent max health from being set below 0 * Add more detail to comment --- .../java/org/geysermc/geyser/entity/type/LivingEntity.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java index 0cce0f8df..87b709309 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java @@ -300,7 +300,9 @@ public class LivingEntity extends Entity { if (javaAttribute.getType() instanceof AttributeType.Builtin type) { switch (type) { case GENERIC_MAX_HEALTH -> { - this.maxHealth = (float) AttributeUtils.calculateValue(javaAttribute); + // Since 1.18.0, setting the max health to 0 or below causes the entity to die on Bedrock but not on Java + // See https://github.com/GeyserMC/Geyser/issues/2971 + this.maxHealth = Math.max((float) AttributeUtils.calculateValue(javaAttribute), 1f); newAttributes.add(createHealthAttribute()); } case GENERIC_ATTACK_DAMAGE -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.ATTACK_DAMAGE)); From d74b0e236d4a5fb78ece87f1e911d06df1530c04 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 26 May 2022 18:04:58 -0400 Subject: [PATCH 110/110] Fix encoding SpawnParticleEffectPacket for 1.18.30+ --- .../geyser/entity/type/living/monster/EnderDragonEntity.java | 2 ++ .../translator/protocol/java/entity/JavaAnimateTranslator.java | 3 +++ .../protocol/java/level/JavaLevelParticlesTranslator.java | 2 ++ 3 files changed, 7 insertions(+) diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonEntity.java index 99ab1a55c..1d689e806 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonEntity.java @@ -39,6 +39,7 @@ import org.geysermc.geyser.entity.type.living.MobEntity; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.util.DimensionUtils; +import java.util.Optional; import java.util.Random; import java.util.UUID; import java.util.concurrent.ThreadLocalRandom; @@ -262,6 +263,7 @@ public class EnderDragonEntity extends MobEntity implements Tickable { spawnParticleEffectPacket.setDimensionId(DimensionUtils.javaToBedrock(session.getDimension())); spawnParticleEffectPacket.setPosition(head.getPosition().add(random.nextGaussian() / 2f, random.nextGaussian() / 2f, random.nextGaussian() / 2f)); spawnParticleEffectPacket.setIdentifier("minecraft:dragon_breath_fire"); + spawnParticleEffectPacket.setMolangVariablesJson(Optional.empty()); session.sendUpstreamPacket(spawnParticleEffectPacket); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaAnimateTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaAnimateTranslator.java index b9cbebb22..559964f63 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaAnimateTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaAnimateTranslator.java @@ -36,6 +36,8 @@ import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; import org.geysermc.geyser.util.DimensionUtils; +import java.util.Optional; + @Translator(packet = ClientboundAnimatePacket.class) public class JavaAnimateTranslator extends PacketTranslator { @@ -77,6 +79,7 @@ public class JavaAnimateTranslator extends PacketTranslator