From ae70dbeece0094814b80ed2edd9e39add53b033d Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Sat, 17 Oct 2020 23:13:04 -0400 Subject: [PATCH 001/116] JavaOpenWindowTranslator: Use MessageUtils for inventory name (#1416) * JavaOpenWindowTranslator: Use MessageUtils for inventory name * Remove important messaging --- .../java/window/JavaOpenWindowTranslator.java | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java index 820639b3..099de317 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java @@ -25,11 +25,9 @@ package org.geysermc.connector.network.translators.java.window; +import com.github.steveice10.mc.protocol.data.message.MessageSerializer; import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientCloseWindowPacket; import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerOpenWindowPacket; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; @@ -37,6 +35,7 @@ import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.network.translators.inventory.InventoryTranslator; import org.geysermc.connector.utils.InventoryUtils; import org.geysermc.connector.utils.LocaleUtils; +import org.geysermc.connector.utils.MessageUtils; @Translator(packet = ServerOpenWindowPacket.class) public class JavaOpenWindowTranslator extends PacketTranslator { @@ -58,18 +57,8 @@ public class JavaOpenWindowTranslator extends PacketTranslator Date: Sat, 17 Oct 2020 23:50:41 -0400 Subject: [PATCH 002/116] Fix mob mount positions (#1392) * Fix mob mount positions Uses offsets from Java Edition. * Fix Boat mount pos for multiple passengers and Fix Ravager Remove unnecessary horse metadata * Fix Minecart & Boat Mount Pos, Fix Player Height Offset * Use offset of EntityType.PLAYER * Add back metadata --- .../JavaEntitySetPassengersTranslator.java | 170 ++++++++++++++---- 1 file changed, 133 insertions(+), 37 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntitySetPassengersTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntitySetPassengersTranslator.java index 094d64df..64f0e3e9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntitySetPassengersTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntitySetPassengersTranslator.java @@ -82,7 +82,6 @@ public class JavaEntitySetPassengersTranslator extends PacketTranslator 1)); rider = false; } @@ -90,6 +89,9 @@ public class JavaEntitySetPassengersTranslator extends PacketTranslator 1)); + this.updateOffset(passenger, entity, session, false, false, (packet.getPassengerIds().length > 1)); + } else { + this.updateOffset(passenger, entity, session, (packet.getPassengerIds()[0] == passengerId), true, (packet.getPassengerIds().length > 1)); } // Force an update to the passenger metadata passenger.updateBedrockMetadata(session); } - if (entity.getEntityType() == EntityType.HORSE) { - entity.getMetadata().put(EntityData.RIDER_SEAT_POSITION, Vector3f.from(0.0f, 2.3200102f, -0.2f)); - entity.getMetadata().put(EntityData.RIDER_MAX_ROTATION, 181.0f); - - entity.updateBedrockMetadata(session); + switch (entity.getEntityType()) { + case HORSE: + case SKELETON_HORSE: + case DONKEY: + case MULE: + case RAVAGER: + entity.getMetadata().put(EntityData.RIDER_MAX_ROTATION, 181.0f); + entity.updateBedrockMetadata(session); + break; } } - private void updateOffset(Entity passenger, EntityType mountType, GeyserSession session, boolean rider, boolean riding, boolean moreThanOneEntity) { - // Without the Y offset, Bedrock players will find themselves in the floor when mounting - float yOffset = 0; + private float getMountedHeightOffset(Entity mount) { + final EntityType mountType = mount.getEntityType(); + float mountedHeightOffset = mountType.getHeight() * 0.75f; switch (mountType) { - case BOAT: - yOffset = passenger.getEntityType() == EntityType.PLAYER ? 1.02001f : -0.2f; - break; - case MINECART: - yOffset = passenger.getEntityType() == EntityType.PLAYER ? 1.02001f : 0f; + case CHICKEN: + case SPIDER: + mountedHeightOffset = mountType.getHeight() * 0.5f; break; case DONKEY: - yOffset = 2.1f; - break; - case HORSE: - case SKELETON_HORSE: - case ZOMBIE_HORSE: case MULE: - yOffset = 2.3f; + mountedHeightOffset -= 0.25f; break; case LLAMA: - case TRADER_LLAMA: - yOffset = 2.5f; + mountedHeightOffset = mountType.getHeight() * 0.67f; break; - case PIG: - yOffset = 1.85001f; + case MINECART: + case MINECART_HOPPER: + case MINECART_TNT: + case MINECART_CHEST: + case MINECART_FURNACE: + case MINECART_SPAWNER: + case MINECART_COMMAND_BLOCK: + mountedHeightOffset = 0; break; - case ARMOR_STAND: - yOffset = 1.3f; + case BOAT: + mountedHeightOffset = -0.1f; + break; + case HOGLIN: + case ZOGLIN: + boolean isBaby = mount.getMetadata().getFlags().getFlag(EntityFlag.BABY); + mountedHeightOffset = mountType.getHeight() - (isBaby ? 0.2f : 0.15f); + break; + case PIGLIN: + mountedHeightOffset = mountType.getHeight() * 0.92f; + break; + case RAVAGER: + mountedHeightOffset = 2.1f; + break; + case SKELETON_HORSE: + mountedHeightOffset -= 0.1875f; break; case STRIDER: - yOffset = passenger.getEntityType() == EntityType.PLAYER ? 2.8200102f : 1.6f; + mountedHeightOffset = mountType.getHeight() - 0.19f; break; } - Vector3f offset = Vector3f.from(0f, yOffset, 0f); - if (mountType == EntityType.STRIDER) { - offset = offset.add(0f, 0f, -0.2f); - } - // Without the X offset, more than one entity on a boat is stacked on top of each other - if (rider && moreThanOneEntity) { - offset = offset.add(Vector3f.from(0.2, 0, 0)); - } else if (moreThanOneEntity) { - offset = offset.add(Vector3f.from(-0.6, 0, 0)); + return mountedHeightOffset; + } + + private float getHeightOffset(Entity passenger) { + boolean isBaby; + switch (passenger.getEntityType()) { + case SKELETON: + case STRAY: + case WITHER_SKELETON: + return -0.6f; + case ARMOR_STAND: + // Armor stand isn't a marker + if (passenger.getMetadata().getFloat(EntityData.BOUNDING_BOX_HEIGHT) != 0.0f) { + return 0.1f; + } else { + return 0.0f; + } + case ENDERMITE: + case SILVERFISH: + return 0.1f; + case PIGLIN: + case PIGLIN_BRUTE: + case ZOMBIFIED_PIGLIN: + isBaby = passenger.getMetadata().getFlags().getFlag(EntityFlag.BABY); + return isBaby ? -0.05f : -0.45f; + case ZOMBIE: + isBaby = passenger.getMetadata().getFlags().getFlag(EntityFlag.BABY); + return isBaby ? 0.0f : -0.45f; + case EVOKER: + case ILLUSIONER: + case PILLAGER: + case RAVAGER: + case VINDICATOR: + case WITCH: + return -0.45f; + case PLAYER: + return -0.35f; } + return 0f; + } + + private void updateOffset(Entity passenger, Entity mount, GeyserSession session, boolean rider, boolean riding, boolean moreThanOneEntity) { passenger.getMetadata().getFlags().setFlag(EntityFlag.RIDING, riding); if (riding) { + // Without the Y offset, Bedrock players will find themselves in the floor when mounting + float mountedHeightOffset = getMountedHeightOffset(mount); + float heightOffset = getHeightOffset(passenger); + + float xOffset = 0; + float yOffset = mountedHeightOffset + heightOffset; + float zOffset = 0; + switch (mount.getEntityType()) { + case BOAT: + // Without the X offset, more than one entity on a boat is stacked on top of each other + if (rider && moreThanOneEntity) { + xOffset = 0.2f; + } else if (moreThanOneEntity) { + xOffset = -0.6f; + } + break; + case CHICKEN: + zOffset = -0.1f; + break; + case LLAMA: + zOffset = -0.3f; + break; + } + /* + * Bedrock Differences + * Zoglin & Hoglin seem to be taller in Bedrock edition + * Horses are tinier + * Players, Minecarts, and Boats have different origins + */ + if (passenger.getEntityType() == EntityType.PLAYER) { + yOffset += EntityType.PLAYER.getOffset(); + } + switch (mount.getEntityType()) { + case MINECART: + case MINECART_HOPPER: + case MINECART_TNT: + case MINECART_CHEST: + case MINECART_FURNACE: + case MINECART_SPAWNER: + case MINECART_COMMAND_BLOCK: + case BOAT: + yOffset -= mount.getEntityType().getHeight() * 0.5f; + } + Vector3f offset = Vector3f.from(xOffset, yOffset, zOffset); passenger.getMetadata().put(EntityData.RIDER_SEAT_POSITION, offset); } passenger.updateBedrockMetadata(session); From 0635605a24448ccec5b0498275a03038ba073025 Mon Sep 17 00:00:00 2001 From: DaPorkchop_ Date: Sun, 18 Oct 2020 16:59:37 +0200 Subject: [PATCH 003/116] fix chunk section decoding (#1418) * fix chunk section decoding * switch back to official MCProtocolLib --- connector/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/pom.xml b/connector/pom.xml index 5df52556..cf2b8994 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -111,7 +111,7 @@ com.github.steveice10 mcprotocollib - 3a69a0614c + 8270ec65e3 compile From 45429a9357657024da2c914cb57220fe14ee650f Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Sun, 18 Oct 2020 23:29:11 -0400 Subject: [PATCH 004/116] SettingsUtils: fix 'show coordinates' setting persistence (#1429) The boolean that toggled this was accidentally in the wrong spot. --- .../main/java/org/geysermc/connector/utils/SettingsUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java b/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java index 89e9fe67..13db4682 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java @@ -58,7 +58,7 @@ public class SettingsUtils { builder.setIcon(new FormImage(FormImage.FormImageType.PATH, "textures/ui/settings_glyph_color_2x.png")); builder.addComponent(new LabelComponent(LanguageUtils.getPlayerLocaleString("geyser.settings.title.client", language))); - builder.addComponent(new ToggleComponent(LanguageUtils.getPlayerLocaleString("geyser.settings.option.coordinates", language, session.getWorldCache().isShowCoordinates()))); + builder.addComponent(new ToggleComponent(LanguageUtils.getPlayerLocaleString("geyser.settings.option.coordinates", language), session.getWorldCache().isShowCoordinates())); if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.server")) { From 18e2a52d98dd833a362730ceb966494a4a2e5298 Mon Sep 17 00:00:00 2001 From: DaPorkchop_ Date: Mon, 19 Oct 2020 10:43:51 +0200 Subject: [PATCH 005/116] fix decoding sections with duplicate palette entries (#1430) --- connector/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/pom.xml b/connector/pom.xml index cf2b8994..8615ad80 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -111,7 +111,7 @@ com.github.steveice10 mcprotocollib - 8270ec65e3 + 1b01b1ffef compile From b02bc33393788c59d30a8ab30f229bee07aaac12 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Mon, 19 Oct 2020 19:03:31 -0400 Subject: [PATCH 006/116] GeyserSession: Set a default value for attackSpeed (#1419) Fixes cooldowns not showing on a fresh world. --- .../org/geysermc/connector/network/session/GeyserSession.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 1a0bbfb2..79949a5f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -194,9 +194,10 @@ public class GeyserSession implements CommandSender { /** * 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. */ @Setter - private double attackSpeed; + private double attackSpeed = 4.0d; /** * The time of the last hit. Used to gauge how long the cooldown is taking. * This is a session variable in order to prevent more scheduled threads than necessary. From 7f5fac38c6466b1ad33e85def64dc9c245160135 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Mon, 19 Oct 2020 19:09:16 -0400 Subject: [PATCH 007/116] Update to Adventure 4.1.1 (#1410) * Update to Adventure 4.0.0 * Update to 4.0.1 * Update again, I guess. --- connector/pom.xml | 12 ++++++------ .../item/translators/nbt/BasicItemTranslator.java | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/connector/pom.xml b/connector/pom.xml index 8615ad80..19731987 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -137,21 +137,21 @@ 2.1.3 - com.github.kyoripowered.adventure + net.kyori adventure-api - 557865caef + 4.1.1 compile - com.github.kyoripowered.adventure + net.kyori adventure-text-serializer-gson - 557865caef + 4.1.1 compile - com.github.kyoripowered.adventure + net.kyori adventure-text-serializer-legacy - 557865caef + 4.1.1 compile diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BasicItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BasicItemTranslator.java index e4556626..1d21bbfb 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BasicItemTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BasicItemTranslator.java @@ -101,7 +101,7 @@ public class BasicItemTranslator extends NbtItemStackTranslator { if (message.startsWith("§r")) { message = message.replaceFirst("§r", ""); } - Component component = TextComponent.of(message); + Component component = Component.text(message); return GsonComponentSerializer.gson().serialize(component); } From 62d984da611b6cada4a6813a0f2655001e2a3fc8 Mon Sep 17 00:00:00 2001 From: Redned Date: Mon, 19 Oct 2020 20:56:01 -0500 Subject: [PATCH 008/116] Make userAuths information more clear --- connector/src/main/resources/config.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/connector/src/main/resources/config.yml b/connector/src/main/resources/config.yml index 0602bb54..43e3e8ed 100644 --- a/connector/src/main/resources/config.yml +++ b/connector/src/main/resources/config.yml @@ -38,18 +38,18 @@ remote: # You can ignore this when not using Floodgate. floodgate-key-file: public-key.pem -## the Xbox/MCPE username is the key for the Java server auth-info -## this allows automatic configuration/login to the remote Java server -## if you are brave/stupid enough to put your Mojang account info into -## a config file +# 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: -# bluerkelp2: # MCPE/Xbox username -# email: not_really_my_email_address_mr_minecrafter53267@gmail.com # Mojang account email address -# password: "this isn't really my password" +# BedrockAccountUsername: # Your Minecraft: Bedrock Edition username +# email: javaccountemail@example.com # Your Minecraft: Java Edition email +# password: javaccountpassword123 # Your Minecraft: Java Edition password # -# herpderp40300499303040503030300500293858393589: -# email: herpderp@derpherp.com -# password: dooooo +# bluerkelp2: +# email: not_really_my_email_address_mr_minecrafter53267@gmail.com +# password: "this isn't really my password" # 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. From 7f2b2e09133efa004964e807cd8e5a8a996335e2 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Fri, 23 Oct 2020 05:01:03 +0100 Subject: [PATCH 009/116] Bedrock <-> Bedrock skin display fix (#1195) * Implement partial bedrock skin fix * Fix equals method * Fix ViaVersion Co-authored-by: DoctorMacc --- .../connector/utils/SkinProvider.java | 22 ++++++++++++------- .../geysermc/connector/utils/SkinUtils.java | 12 +++++++++- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java b/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java index 7c30e48a..82fc3a3a 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java @@ -33,6 +33,7 @@ import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.network.session.GeyserSession; import javax.imageio.ImageIO; import java.awt.*; @@ -42,13 +43,7 @@ import java.net.HttpURLConnection; import java.net.URL; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; -import java.nio.file.Paths; -import java.util.Arrays; -import java.util.Base64; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.UUID; +import java.util.*; import java.util.concurrent.*; public class SkinProvider { @@ -157,10 +152,21 @@ public class SkinProvider { public static CompletableFuture requestSkinAndCape(UUID playerId, String skinUrl, String capeUrl) { return CompletableFuture.supplyAsync(() -> { long time = System.currentTimeMillis(); + String newSkinUrl = skinUrl; + + if ("steve".equals(skinUrl) || "alex".equals(skinUrl)) { + // TODO: Don't have a for loop for this? Have a proper map? + for (GeyserSession session : GeyserConnector.getInstance().getPlayers()) { + if (session.getPlayerEntity().getUuid().equals(playerId)) { + newSkinUrl = session.getClientData().getSkinId(); + break; + } + } + } CapeProvider provider = capeUrl != null ? CapeProvider.MINECRAFT : null; SkinAndCape skinAndCape = new SkinAndCape( - getOrDefault(requestSkin(playerId, skinUrl, false), EMPTY_SKIN, 5), + getOrDefault(requestSkin(playerId, newSkinUrl, false), EMPTY_SKIN, 5), getOrDefault(requestCape(capeUrl, provider, false), EMPTY_CAPE, 5) ); diff --git a/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java b/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java index 5505acdf..d65dbc81 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java @@ -251,6 +251,7 @@ public class SkinUtils { try { GameProfile.Property skinProperty = profile.getProperty("textures"); + // TODO: Remove try/catch here JsonNode skinObject = new ObjectMapper().readTree(new String(Base64.getDecoder().decode(skinProperty.getValue()), StandardCharsets.UTF_8)); JsonNode textures = skinObject.get("textures"); @@ -271,7 +272,16 @@ public class SkinUtils { GeyserConnector.getInstance().getLogger().debug("Got invalid texture data for " + profile.getName() + " " + exception.getMessage()); } // return default skin with default cape when texture data is invalid - return new GameProfileData((isAlex ? SkinProvider.EMPTY_SKIN_ALEX.getTextureUrl() : SkinProvider.EMPTY_SKIN.getTextureUrl()), SkinProvider.EMPTY_CAPE.getTextureUrl(), isAlex); + String skinUrl = isAlex ? SkinProvider.EMPTY_SKIN_ALEX.getTextureUrl() : SkinProvider.EMPTY_SKIN.getTextureUrl(); + if ("steve".equals(skinUrl) || "alex".equals(skinUrl)) { + for (GeyserSession session : GeyserConnector.getInstance().getPlayers()) { + if (session.getPlayerEntity().getUuid().equals(profile.getId())) { + skinUrl = session.getClientData().getSkinId(); + break; + } + } + } + return new GameProfileData(skinUrl, SkinProvider.EMPTY_CAPE.getTextureUrl(), isAlex); } } } From ee8c718c621be0b6c3cde594a7b45004fd70e2e9 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Fri, 23 Oct 2020 01:25:24 -0500 Subject: [PATCH 010/116] Translate emote list packet --- .../network/session/GeyserSession.java | 24 +++++++++++ .../bedrock/BedrockEmoteListTranslator.java | 40 +++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockEmoteListTranslator.java diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 79949a5f..6b0f73ae 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -275,6 +275,10 @@ public class GeyserSession implements CommandSender { @Setter private String lastSignMessage; + @Setter + private List selectedEmotes = new ArrayList<>(); + private final Set emotes = new HashSet<>(); + private MinecraftProtocol protocol; public GeyserSession(GeyserConnector connector, BedrockServerSession bedrockServerSession) { @@ -295,6 +299,8 @@ public class GeyserSession implements CommandSender { this.inventoryCache.getInventories().put(0, inventory); + connector.getPlayers().forEach(player -> this.emotes.addAll(player.getEmotes())); + bedrockServerSession.addDisconnectHandler(disconnectReason -> { connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.disconnect", bedrockServerSession.getAddress().getAddress(), disconnectReason)); @@ -769,4 +775,22 @@ public class GeyserSession implements CommandSender { adventureSettingsPacket.getSettings().addAll(flags); sendUpstreamPacket(adventureSettingsPacket); } + + public void refreshEmotes(List emotes) { + this.selectedEmotes = emotes; + this.emotes.addAll(emotes); + for (GeyserSession player : connector.getPlayers()) { + List pieces = new ArrayList<>(); + for (UUID piece : emotes) { + if (!player.getEmotes().contains(piece)) { + this.emotes.add(piece); + } + pieces.add(piece); + } + EmoteListPacket emoteList = new EmoteListPacket(); + emoteList.setRuntimeEntityId(player.getPlayerEntity().getGeyserId()); + emoteList.getPieceIds().addAll(pieces); + player.sendUpstreamPacket(emoteList); + } + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockEmoteListTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockEmoteListTranslator.java new file mode 100644 index 00000000..ef5b1a56 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockEmoteListTranslator.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2019-2020 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.connector.network.translators.bedrock; + +import com.nukkitx.protocol.bedrock.packet.EmoteListPacket; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; + +@Translator(packet = EmoteListPacket.class) +public class BedrockEmoteListTranslator extends PacketTranslator { + + @Override + public void translate(EmoteListPacket packet, GeyserSession session) { + session.refreshEmotes(packet.getPieceIds()); + } +} From dfba278f4d16acdef839af431645bcb70182c142 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Fri, 23 Oct 2020 01:36:34 -0500 Subject: [PATCH 011/116] Use correct methods in refreshEmotes --- .../org/geysermc/connector/network/session/GeyserSession.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 6b0f73ae..bb7602f3 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -783,9 +783,9 @@ public class GeyserSession implements CommandSender { List pieces = new ArrayList<>(); for (UUID piece : emotes) { if (!player.getEmotes().contains(piece)) { - this.emotes.add(piece); + pieces.add(piece); } - pieces.add(piece); + player.getEmotes().add(piece); } EmoteListPacket emoteList = new EmoteListPacket(); emoteList.setRuntimeEntityId(player.getPlayerEntity().getGeyserId()); From c30cb78e74921c611f3babfdcc8f6b96aa449232 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Sat, 24 Oct 2020 23:33:49 +0100 Subject: [PATCH 012/116] Add statistics menu (#1424) * Add statistics menu * Changed back button text * Add check to make sure the player requested the statistics display * Better item translation support; misc changes * Clean up session getting? * Remove extra debug that is likely unnecessary * Remove unused function * Update languages submodule * Clean up javadoc comment * Fix typo Co-authored-by: DoctorMacc Co-authored-by: Camotoy <20743703+DoctorMacc@users.noreply.github.com> --- .../common/window/CustomFormWindow.java | 4 +- .../common/window/SimpleFormWindow.java | 4 +- .../connector/command/CommandManager.java | 1 + .../command/defaults/StatisticsCommand.java | 69 ++++++ .../network/UpstreamPacketHandler.java | 5 + .../network/session/GeyserSession.java | 23 ++ .../java/JavaStatisticsTranslator.java | 46 ++++ .../world/block/BlockTranslator.java | 11 + .../connector/utils/StatisticsUtils.java | 233 ++++++++++++++++++ connector/src/main/resources/languages | 2 +- 10 files changed, 393 insertions(+), 5 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/command/defaults/StatisticsCommand.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/java/JavaStatisticsTranslator.java create mode 100644 connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java diff --git a/common/src/main/java/org/geysermc/common/window/CustomFormWindow.java b/common/src/main/java/org/geysermc/common/window/CustomFormWindow.java index efc71ae8..045552b6 100644 --- a/common/src/main/java/org/geysermc/common/window/CustomFormWindow.java +++ b/common/src/main/java/org/geysermc/common/window/CustomFormWindow.java @@ -92,7 +92,7 @@ public class CustomFormWindow extends FormWindow { } public void setResponse(String data) { - if (data == null || data.equalsIgnoreCase("null") || data.isEmpty()) { + if (data == null || data.trim().equalsIgnoreCase("null") || data.isEmpty()) { closed = true; return; } @@ -108,7 +108,7 @@ public class CustomFormWindow extends FormWindow { List componentResponses = new ArrayList<>(); try { - componentResponses = new ObjectMapper().readValue(data, new TypeReference>(){}); + componentResponses = new ObjectMapper().readValue(data.trim(), new TypeReference>(){}); } catch (IOException e) { } for (String response : componentResponses) { diff --git a/common/src/main/java/org/geysermc/common/window/SimpleFormWindow.java b/common/src/main/java/org/geysermc/common/window/SimpleFormWindow.java index 7c1acc26..3101f5fb 100644 --- a/common/src/main/java/org/geysermc/common/window/SimpleFormWindow.java +++ b/common/src/main/java/org/geysermc/common/window/SimpleFormWindow.java @@ -72,14 +72,14 @@ public class SimpleFormWindow extends FormWindow { } public void setResponse(String data) { - if (data == null || data.equalsIgnoreCase("null")) { + if (data == null || data.trim().equalsIgnoreCase("null")) { closed = true; return; } int buttonID; try { - buttonID = Integer.parseInt(data); + buttonID = Integer.parseInt(data.trim()); } catch (Exception ex) { return; } diff --git a/connector/src/main/java/org/geysermc/connector/command/CommandManager.java b/connector/src/main/java/org/geysermc/connector/command/CommandManager.java index afa75503..2b35424a 100644 --- a/connector/src/main/java/org/geysermc/connector/command/CommandManager.java +++ b/connector/src/main/java/org/geysermc/connector/command/CommandManager.java @@ -52,6 +52,7 @@ public abstract class CommandManager { registerCommand(new OffhandCommand(connector, "offhand", LanguageUtils.getLocaleStringLog("geyser.commands.offhand.desc"), "geyser.command.offhand")); registerCommand(new DumpCommand(connector, "dump", LanguageUtils.getLocaleStringLog("geyser.commands.dump.desc"), "geyser.command.dump")); registerCommand(new VersionCommand(connector, "version", LanguageUtils.getLocaleStringLog("geyser.commands.version.desc"), "geyser.command.version")); + registerCommand(new StatisticsCommand(connector, "statistics", LanguageUtils.getLocaleStringLog("geyser.commands.statistics.desc"), "geyser.command.statistics")); } public void registerCommand(GeyserCommand command) { diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/StatisticsCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/StatisticsCommand.java new file mode 100644 index 00000000..ed9db58f --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/StatisticsCommand.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2019-2020 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.connector.command.defaults; + +import com.github.steveice10.mc.protocol.data.game.ClientRequest; +import com.github.steveice10.mc.protocol.packet.ingame.client.ClientRequestPacket; +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.command.CommandSender; +import org.geysermc.connector.command.GeyserCommand; +import org.geysermc.connector.network.session.GeyserSession; + +public class StatisticsCommand extends GeyserCommand { + + private final GeyserConnector connector; + + public StatisticsCommand(GeyserConnector connector, String name, String description, String permission) { + super(name, description, permission); + + this.connector = connector; + } + + @Override + public void execute(CommandSender sender, String[] args) { + if (sender.isConsole()) { + return; + } + + // Make sure the sender is a Bedrock edition client + GeyserSession session = null; + if (sender instanceof GeyserSession) { + session = (GeyserSession) sender; + } else { + // Needed for Spigot - sender is not an instance of GeyserSession + for (GeyserSession otherSession : connector.getPlayers()) { + if (sender.getName().equals(otherSession.getPlayerEntity().getUsername())) { + session = otherSession; + break; + } + } + } + if (session == null) return; + session.setWaitingForStatistics(true); + ClientRequestPacket clientRequestPacket = new ClientRequestPacket(ClientRequest.STATS); + session.sendDownstreamPacket(clientRequestPacket); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java index f76d64ed..f99abbe5 100644 --- a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java @@ -40,6 +40,7 @@ import org.geysermc.connector.utils.MathUtils; import org.geysermc.connector.utils.ResourcePack; import org.geysermc.connector.utils.ResourcePackManifest; import org.geysermc.connector.utils.SettingsUtils; +import org.geysermc.connector.utils.StatisticsUtils; import java.io.FileInputStream; import java.io.InputStream; @@ -141,6 +142,10 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { public boolean handle(ModalFormResponsePacket packet) { if (packet.getFormId() == SettingsUtils.SETTINGS_FORM_ID) { return SettingsUtils.handleSettingsForm(session, packet.getFormData()); + } else if (packet.getFormId() == StatisticsUtils.STATISTICS_MENU_FORM_ID) { + return StatisticsUtils.handleMenuForm(session, packet.getFormData()); + } else if (packet.getFormId() == StatisticsUtils.STATISTICS_LIST_FORM_ID) { + return StatisticsUtils.handleListForm(session, packet.getFormData()); } return LoginEncryptionUtils.authenticateFromForm(session, connector, packet.getFormId(), packet.getFormData()); diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index bb7602f3..a8a4adb9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -32,6 +32,7 @@ import com.github.steveice10.mc.protocol.MinecraftConstants; import com.github.steveice10.mc.protocol.MinecraftProtocol; import com.github.steveice10.mc.protocol.data.SubProtocol; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; +import com.github.steveice10.mc.protocol.data.game.statistic.Statistic; import com.github.steveice10.mc.protocol.data.game.window.VillagerTrade; import com.github.steveice10.mc.protocol.data.message.MessageSerializer; import com.github.steveice10.mc.protocol.packet.handshake.client.HandshakePacket; @@ -55,6 +56,7 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2LongMap; import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap; import lombok.Getter; +import lombok.NonNull; import lombok.Setter; import org.geysermc.common.window.CustomFormWindow; import org.geysermc.common.window.FormWindow; @@ -275,6 +277,18 @@ public class GeyserSession implements CommandSender { @Setter private String lastSignMessage; + /** + * Stores a map of all statistics sent from the server. + * The server only sends new statistics back to us, so in order to show all statistics we need to cache existing ones. + */ + private final Map statistics = new HashMap<>(); + + /** + * Whether we're expecting statistics to be sent back to us. + */ + @Setter + private boolean waitingForStatistics = false; + @Setter private List selectedEmotes = new ArrayList<>(); private final Set emotes = new HashSet<>(); @@ -776,6 +790,15 @@ public class GeyserSession implements CommandSender { sendUpstreamPacket(adventureSettingsPacket); } + /** + * Used for updating statistic values since we only get changes from the server + * + * @param statistics Updated statistics values + */ + public void updateStatistics(@NonNull Map statistics) { + this.statistics.putAll(statistics); + } + public void refreshEmotes(List emotes) { this.selectedEmotes = emotes; this.emotes.addAll(emotes); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaStatisticsTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaStatisticsTranslator.java new file mode 100644 index 00000000..9a80254b --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaStatisticsTranslator.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2019-2020 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.connector.network.translators.java; + +import com.github.steveice10.mc.protocol.packet.ingame.server.ServerStatisticsPacket; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; +import org.geysermc.connector.utils.StatisticsUtils; + +@Translator(packet = ServerStatisticsPacket.class) +public class JavaStatisticsTranslator extends PacketTranslator { + + @Override + public void translate(ServerStatisticsPacket packet, GeyserSession session) { + session.updateStatistics(packet.getStatistics()); + + if (session.isWaitingForStatistics()) { + session.setWaitingForStatistics(false); + session.sendForm(StatisticsUtils.buildMenuForm(session), StatisticsUtils.STATISTICS_MENU_FORM_ID); + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java index e5f8d6aa..5314292a 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java @@ -67,6 +67,11 @@ public class BlockTranslator { public static final Int2BooleanMap JAVA_RUNTIME_ID_TO_CAN_HARVEST_WITH_HAND = new Int2BooleanOpenHashMap(); public static final Int2ObjectMap JAVA_RUNTIME_ID_TO_TOOL_TYPE = new Int2ObjectOpenHashMap<>(); + /** + * Java numeric ID to java unique identifier, used for block names in the statistics screen + */ + public static final Int2ObjectMap JAVA_ID_TO_JAVA_IDENTIFIER_MAP = new Int2ObjectOpenHashMap<>(); + /** * Runtime command block ID, used for fixing command block minecart appearances */ @@ -124,6 +129,7 @@ public class BlockTranslator { int furnaceRuntimeId = -1; int furnaceLitRuntimeId = -1; int spawnerRuntimeId = -1; + int uniqueJavaId = -1; Iterator> blocksIterator = blocks.fields(); while (blocksIterator.hasNext()) { javaRuntimeId++; @@ -166,6 +172,11 @@ public class BlockTranslator { String cleanJavaIdentifier = entry.getKey().split("\\[")[0]; + if (!JAVA_ID_TO_JAVA_IDENTIFIER_MAP.containsValue(cleanJavaIdentifier)) { + uniqueJavaId++; + JAVA_ID_TO_JAVA_IDENTIFIER_MAP.put(uniqueJavaId, cleanJavaIdentifier); + } + if (!cleanJavaIdentifier.equals(bedrockIdentifier)) { JAVA_TO_BEDROCK_IDENTIFIERS.put(cleanJavaIdentifier, bedrockIdentifier); } diff --git a/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java b/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java new file mode 100644 index 00000000..3c42182d --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2019-2020 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.connector.utils; + +import com.github.steveice10.mc.protocol.data.MagicValues; +import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType; +import com.github.steveice10.mc.protocol.data.game.statistic.*; +import org.geysermc.common.window.SimpleFormWindow; +import org.geysermc.common.window.button.FormButton; +import org.geysermc.common.window.response.SimpleFormResponse; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.item.ItemRegistry; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; + +import java.util.Map; + +public class StatisticsUtils { + + // Used in UpstreamPacketHandler.java + public static final int STATISTICS_MENU_FORM_ID = 1339; + public static final int STATISTICS_LIST_FORM_ID = 1340; + + /** + * Build a form for the given session with all statistic categories + * + * @param session The session to build the form for + */ + public static SimpleFormWindow buildMenuForm(GeyserSession session) { + // Cache the language for cleaner access + String language = session.getClientData().getLanguageCode(); + + SimpleFormWindow window = new SimpleFormWindow(LocaleUtils.getLocaleString("gui.stats", language), ""); + + window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.generalButton", language))); + + window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.mined", language))); + window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.broken", language))); + window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.crafted", language))); + window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.used", language))); + window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.picked_up", language))); + window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.dropped", language))); + + window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.mobsButton", language) + " - " + LanguageUtils.getPlayerLocaleString("geyser.statistics.killed", language))); + window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.mobsButton", language) + " - " + LanguageUtils.getPlayerLocaleString("geyser.statistics.killed_by", language))); + + return window; + } + + /** + * Handle the menu form response + * + * @param session The session that sent the response + * @param response The response string to parse + * @return True if the form was parsed correctly, false if not + */ + public static boolean handleMenuForm(GeyserSession session, String response) { + SimpleFormWindow menuForm = (SimpleFormWindow) session.getWindowCache().getWindows().get(STATISTICS_MENU_FORM_ID); + menuForm.setResponse(response); + SimpleFormResponse formResponse = (SimpleFormResponse) menuForm.getResponse(); + + // Cache the language for cleaner access + String language = session.getClientData().getLanguageCode(); + + if (formResponse != null && formResponse.getClickedButton() != null) { + String title; + StringBuilder content = new StringBuilder(); + + switch (formResponse.getClickedButtonId()) { + case 0: + title = LocaleUtils.getLocaleString("stat.generalButton", language); + + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof GenericStatistic) { + content.append(LocaleUtils.getLocaleString("stat.minecraft." + ((GenericStatistic) entry.getKey()).name().toLowerCase(), language) + ": " + entry.getValue() + "\n"); + } + } + break; + case 1: + title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.mined", language); + + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof BreakBlockStatistic) { + String block = BlockTranslator.JAVA_ID_TO_JAVA_IDENTIFIER_MAP.get(((BreakBlockStatistic) entry.getKey()).getId()); + block = block.replace("minecraft:", "block.minecraft."); + block = LocaleUtils.getLocaleString(block, language); + content.append(block + ": " + entry.getValue() + "\n"); + } + } + break; + case 2: + title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.broken", language); + + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof BreakItemStatistic) { + String item = ItemRegistry.ITEM_ENTRIES.get(((BreakItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); + content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n"); + } + } + break; + case 3: + title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.crafted", language); + + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof CraftItemStatistic) { + String item = ItemRegistry.ITEM_ENTRIES.get(((CraftItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); + content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n"); + } + } + break; + case 4: + title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.used", language); + + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof UseItemStatistic) { + String item = ItemRegistry.ITEM_ENTRIES.get(((UseItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); + content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n"); + } + } + break; + case 5: + title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.picked_up", language); + + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof PickupItemStatistic) { + String item = ItemRegistry.ITEM_ENTRIES.get(((PickupItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); + content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n"); + } + } + break; + case 6: + title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.dropped", language); + + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof DropItemStatistic) { + String item = ItemRegistry.ITEM_ENTRIES.get(((DropItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); + content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n"); + } + } + break; + case 7: + title = LocaleUtils.getLocaleString("stat.mobsButton", language) + " - " + LanguageUtils.getPlayerLocaleString("geyser.statistics.killed", language); + + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof KillEntityStatistic) { + String mob = LocaleUtils.getLocaleString("entity.minecraft." + MagicValues.key(EntityType.class, ((KillEntityStatistic) entry.getKey()).getId()).name().toLowerCase(), language); + content.append(mob + ": " + entry.getValue() + "\n"); + } + } + break; + case 8: + title = LocaleUtils.getLocaleString("stat.mobsButton", language) + " - " + LanguageUtils.getPlayerLocaleString("geyser.statistics.killed_by", language); + + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof KilledByEntityStatistic) { + String mob = LocaleUtils.getLocaleString("entity.minecraft." + MagicValues.key(EntityType.class, ((KilledByEntityStatistic) entry.getKey()).getId()).name().toLowerCase(), language); + content.append(mob + ": " + entry.getValue() + "\n"); + } + } + break; + default: + return false; + } + + if (content.length() == 0) { + content = new StringBuilder(LanguageUtils.getPlayerLocaleString("geyser.statistics.none", language)); + } + + SimpleFormWindow window = new SimpleFormWindow(title, content.toString()); + window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("gui.back", language))); + session.sendForm(window, STATISTICS_LIST_FORM_ID); + } + + return true; + } + + /** + * Handle the list form response + * + * @param session The session that sent the response + * @param response The response string to parse + * @return True if the form was parsed correctly, false if not + */ + public static boolean handleListForm(GeyserSession session, String response) { + SimpleFormWindow listForm = (SimpleFormWindow) session.getWindowCache().getWindows().get(STATISTICS_LIST_FORM_ID); + listForm.setResponse(response); + + if (!listForm.isClosed()) { + session.sendForm(buildMenuForm(session), STATISTICS_MENU_FORM_ID); + } + + return true; + } + + /** + * Finds the item translation key from the Java locale. + * + * @param item the namespaced item to search for. + * @param language the language to search in + * @return the full name of the item + */ + private static String getItemTranslateKey(String item, String language) { + item = item.replace("minecraft:", "item.minecraft."); + String translatedItem = LocaleUtils.getLocaleString(item, language); + if (translatedItem.equals(item)) { + // Didn't translate; must be a block + translatedItem = LocaleUtils.getLocaleString(item.replace("item.", "block."), language); + } + return translatedItem; + } +} diff --git a/connector/src/main/resources/languages b/connector/src/main/resources/languages index 5f217922..a4125be9 160000 --- a/connector/src/main/resources/languages +++ b/connector/src/main/resources/languages @@ -1 +1 @@ -Subproject commit 5f21792264a364e32425014e0be79db93593da1e +Subproject commit a4125be98fefea6cefd43dc52ccb2ade4e70573e From d93d4d0942381046f9c0e7d63060db7b6ff1c557 Mon Sep 17 00:00:00 2001 From: David Choo Date: Mon, 26 Oct 2020 11:54:37 -0400 Subject: [PATCH 013/116] Projectile fixes (#1451) * Predict the trajectory of projectiles and add particles * Correct lingering potion gravity * Update last position on move absolute * Clean up * Add egg to ItemRegistry and update mappings --- .../connector/entity/ThrowableEntity.java | 54 +++++++++++++++++++ .../translators/item/ItemRegistry.java | 7 +++ .../entity/JavaEntityStatusTranslator.java | 25 +++++++++ connector/src/main/resources/mappings | 2 +- 4 files changed, 87 insertions(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java b/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java index d0632d97..b3632606 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java @@ -26,11 +26,65 @@ package org.geysermc.connector.entity; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.LevelEventType; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; public class ThrowableEntity extends Entity { + private Vector3f lastPosition; + private ScheduledFuture positionUpdater; + public ThrowableEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); + this.lastPosition = position; + } + + @Override + public void spawnEntity(GeyserSession session) { + super.spawnEntity(session); + positionUpdater = session.getConnector().getGeneralThreadPool().scheduleAtFixedRate(() -> { + super.moveRelative(session, motion.getX(), motion.getY(), motion.getZ(), rotation, onGround); + + if (metadata.getFlags().getFlag(EntityFlag.HAS_GRAVITY)) { + float gravity = 0.03f; // Snowball, Egg, and Ender Pearl + if (entityType == EntityType.THROWN_POTION || entityType == EntityType.LINGERING_POTION) { + gravity = 0.05f; + } else if (entityType == EntityType.THROWN_EXP_BOTTLE) { + gravity = 0.07f; + } + motion = motion.down(gravity); + } + }, 0, 50, TimeUnit.MILLISECONDS); + } + + @Override + public boolean despawnEntity(GeyserSession session) { + positionUpdater.cancel(true); + if (entityType == EntityType.THROWN_ENDERPEARL) { + LevelEventPacket particlePacket = new LevelEventPacket(); + particlePacket.setType(LevelEventType.PARTICLE_TELEPORT); + particlePacket.setPosition(position); + session.sendUpstreamPacket(particlePacket); + } + return super.despawnEntity(session); + } + + @Override + public void moveRelative(GeyserSession session, double relX, double relY, double relZ, Vector3f rotation, boolean isOnGround) { + position = lastPosition; + super.moveRelative(session, relX, relY, relZ, rotation, isOnGround); + lastPosition = position; + } + + @Override + public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) { + super.moveAbsolute(session, position, rotation, isOnGround, teleported); + lastPosition = position; } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java index 850e4e05..597c87b2 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java @@ -63,6 +63,10 @@ public class ItemRegistry { * Bucket item entry, used in BedrockInventoryTransactionTranslator.java */ public static ItemEntry BUCKET; + /** + * Egg item entry, used in JavaEntityStatusTranslator.java + */ + public static ItemEntry EGG; /** * Gold item entry, used in PiglinEntity.java */ @@ -141,6 +145,9 @@ public class ItemRegistry { case "minecraft:oak_boat": BOAT = ITEM_ENTRIES.get(itemIndex); break; + case "minecraft:egg": + EGG = ITEM_ENTRIES.get(itemIndex); + break; case "minecraft:gold_ingot": GOLD = ITEM_ENTRIES.get(itemIndex); break; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityStatusTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityStatusTranslator.java index c3fbd2e9..09d0eaa9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityStatusTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityStatusTranslator.java @@ -25,10 +25,16 @@ package org.geysermc.connector.network.translators.java.entity; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.github.steveice10.mc.protocol.data.game.world.particle.ItemParticleData; import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityStatusPacket; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.LevelEventType; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; +import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; +import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket; import com.nukkitx.protocol.bedrock.packet.SetEntityMotionPacket; import org.geysermc.connector.entity.Entity; @@ -36,6 +42,9 @@ import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; +import org.geysermc.connector.network.translators.item.ItemEntry; +import org.geysermc.connector.network.translators.item.ItemRegistry; +import org.geysermc.connector.network.translators.item.ItemTranslator; @Translator(packet = ServerEntityStatusPacket.class) public class JavaEntityStatusTranslator extends PacketTranslator { @@ -88,6 +97,22 @@ public class JavaEntityStatusTranslator extends PacketTranslator Date: Tue, 27 Oct 2020 11:02:25 +0000 Subject: [PATCH 014/116] Use new Open Collaboration maven repository --- pom.xml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index acfdc3d6..345933ac 100644 --- a/pom.xml +++ b/pom.xml @@ -42,8 +42,8 @@ https://jitpack.io - nukkitx-release-repo - https://repo.nukkitx.com/maven-releases/ + opencollab-release-repo + https://repo.opencollab.dev/maven-releases/ true @@ -52,8 +52,8 @@ - nukkitx-snapshot-repo - https://repo.nukkitx.com/maven-snapshots/ + opencollab-snapshot-repo + https://repo.opencollab.dev/maven-snapshots/ false @@ -74,13 +74,13 @@ releases - nukkitx-releases - https://repo.nukkitx.com/maven-releases + opencollab-releases + https://repo.opencollab.dev/maven-releases snapshots - nukkitx-snapshots - https://repo.nukkitx.com/maven-snapshots + opencollab-snapshots + https://repo.opencollab.dev/maven-snapshots From a2a7e99402d3f074141f7b70f49212ed720a740d Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Tue, 27 Oct 2020 18:40:00 -0400 Subject: [PATCH 015/116] GUI Improvements (#1462) - Added `GeyserCommand.isExecutableOnConsole()`. If this is set to false, the command will not appear as an option in the GUI. - Added `GeyserCommand.getSubCommands()`. If not empty, the subcommand options will now appear in the GUI. --- .gitignore | 3 +- .../standalone/gui/GeyserStandaloneGUI.java | 24 +++++++++++++--- .../connector/command/GeyserCommand.java | 28 +++++++++++++++++++ .../command/defaults/DumpCommand.java | 11 ++++++-- .../command/defaults/OffhandCommand.java | 5 ++++ .../command/defaults/StatisticsCommand.java | 5 ++++ 6 files changed, 68 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index e3c3b0a5..85f8a6e9 100644 --- a/.gitignore +++ b/.gitignore @@ -242,4 +242,5 @@ logs/ public-key.pem locales/ /cache/ -/packs/ \ No newline at end of file +/packs/ +/dump.json \ No newline at end of file diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/GeyserStandaloneGUI.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/GeyserStandaloneGUI.java index 50deeb1b..aeee8462 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/GeyserStandaloneGUI.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/GeyserStandaloneGUI.java @@ -261,14 +261,30 @@ public class GeyserStandaloneGUI { for (Map.Entry command : geyserCommandManager.getCommands().entrySet()) { // Remove the offhand command and any alias commands to prevent duplicates in the list - if ("offhand".equals(command.getValue().getName()) || command.getValue().getAliases().contains(command.getKey())) { + if (!command.getValue().isExecutableOnConsole() || command.getValue().getAliases().contains(command.getKey())) { continue; } // Create the button that runs the command - JMenuItem commandButton = new JMenuItem(command.getValue().getName()); + boolean hasSubCommands = command.getValue().hasSubCommands(); + // Add an extra menu if there are more commands that can be run + JMenuItem commandButton = hasSubCommands ? new JMenu(command.getValue().getName()) : new JMenuItem(command.getValue().getName()); commandButton.getAccessibleContext().setAccessibleDescription(command.getValue().getDescription()); - commandButton.addActionListener(e -> command.getValue().execute(geyserStandaloneLogger, new String[]{ })); + if (!hasSubCommands) { + commandButton.addActionListener(e -> command.getValue().execute(geyserStandaloneLogger, new String[]{ })); + } else { + // Add a submenu that's the same name as the menu can't be pressed + JMenuItem otherCommandButton = new JMenuItem(command.getValue().getName()); + otherCommandButton.getAccessibleContext().setAccessibleDescription(command.getValue().getDescription()); + otherCommandButton.addActionListener(e -> command.getValue().execute(geyserStandaloneLogger, new String[]{ })); + commandButton.add(otherCommandButton); + // Add a menu option for all possible subcommands + for (String subCommandName : command.getValue().getSubCommands()) { + JMenuItem item = new JMenuItem(subCommandName); + item.addActionListener(e -> command.getValue().execute(geyserStandaloneLogger, new String[]{subCommandName})); + commandButton.add(item); + } + } commandsMenu.add(commandButton); } @@ -291,7 +307,7 @@ public class GeyserStandaloneGUI { playerTableModel.getDataVector().removeAllElements(); for (GeyserSession player : GeyserConnector.getInstance().getPlayers()) { - Vector row = new Vector(); + Vector row = new Vector<>(); row.add(player.getSocketAddress().getHostName()); row.add(player.getPlayerEntity().getUsername()); diff --git a/connector/src/main/java/org/geysermc/connector/command/GeyserCommand.java b/connector/src/main/java/org/geysermc/connector/command/GeyserCommand.java index 62bc6c73..2bb8893c 100644 --- a/connector/src/main/java/org/geysermc/connector/command/GeyserCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/GeyserCommand.java @@ -30,6 +30,7 @@ import lombok.RequiredArgsConstructor; import lombok.Setter; import java.util.ArrayList; +import java.util.Collections; import java.util.List; @Getter @@ -44,4 +45,31 @@ public abstract class GeyserCommand { private List aliases = new ArrayList<>(); public abstract void execute(CommandSender sender, String[] args); + + /** + * If false, hides the command from being shown on the Geyser Standalone GUI. + * + * @return true if the command can be run on the server console + */ + public boolean isExecutableOnConsole() { + return true; + } + + /** + * Used in the GUI to know what subcommands can be run + * + * @return a list of all possible subcommands, or empty if none. + */ + public List getSubCommands() { + return Collections.emptyList(); + } + + /** + * Shortcut to {@link #getSubCommands()}{@code .isEmpty()}. + * + * @return true if there are subcommand present for this command. + */ + public boolean hasSubCommands() { + return !getSubCommands().isEmpty(); + } } \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/DumpCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/DumpCommand.java index 9ad0d23d..9103755a 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/DumpCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/DumpCommand.java @@ -27,12 +27,10 @@ package org.geysermc.connector.command.defaults; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter; -import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider; -import org.geysermc.connector.common.ChatColor; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.CommandSender; import org.geysermc.connector.command.GeyserCommand; +import org.geysermc.connector.common.ChatColor; import org.geysermc.connector.common.serializer.AsteriskSerializer; import org.geysermc.connector.dump.DumpInfo; import org.geysermc.connector.utils.LanguageUtils; @@ -40,6 +38,8 @@ import org.geysermc.connector.utils.WebUtils; import java.io.FileOutputStream; import java.io.IOException; +import java.util.Arrays; +import java.util.List; public class DumpCommand extends GeyserCommand { @@ -130,4 +130,9 @@ public class DumpCommand extends GeyserCommand { connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.commands.dump.created", sender.getName(), uploadedDumpUrl)); } } + + @Override + public List getSubCommands() { + return Arrays.asList("offline", "full"); + } } diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/OffhandCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/OffhandCommand.java index b1b60132..d5e0a792 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/OffhandCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/OffhandCommand.java @@ -68,4 +68,9 @@ public class OffhandCommand extends GeyserCommand { } } } + + @Override + public boolean isExecutableOnConsole() { + return false; + } } diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/StatisticsCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/StatisticsCommand.java index ed9db58f..52379145 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/StatisticsCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/StatisticsCommand.java @@ -66,4 +66,9 @@ public class StatisticsCommand extends GeyserCommand { ClientRequestPacket clientRequestPacket = new ClientRequestPacket(ClientRequest.STATS); session.sendDownstreamPacket(clientRequestPacket); } + + @Override + public boolean isExecutableOnConsole() { + return false; + } } From 9b46bf8bc9f08315bf2a8241857a3cfc5d955b54 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Thu, 29 Oct 2020 16:44:45 -0400 Subject: [PATCH 016/116] BedrockActionTranslator: Fix occasional death stall (#1432) Usually this happened when joining from another dimension after the player exited to the main menu on the death screen. The player would not realize that they are dead. --- .../bedrock/entity/player/BedrockActionTranslator.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java index 91caf8d1..e3bcbce9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java @@ -68,6 +68,8 @@ public class BedrockActionTranslator extends PacketTranslator Date: Thu, 29 Oct 2020 18:30:52 -0400 Subject: [PATCH 017/116] Introduce CommandSender.getLocale() (#1431) * Introduce CommandSender.getLocale() This allows Geyser-specific commands (e.g. `/geyser help`) to be displayed in the (Java or Bedrock) player's default language, which stops those commands from simply being displayed in the default locale. * Tweak Javadoc * Set CommandManager's GeyserConnector to final * Clean up --- .../command/BungeeCommandSender.java | 23 ++++-- .../command/GeyserBungeeCommandExecutor.java | 15 ++-- .../platform/spigot/GeyserSpigotPlugin.java | 4 ++ .../command/GeyserSpigotCommandExecutor.java | 13 ++-- .../spigot/command/SpigotCommandSender.java | 71 +++++++++++++++++-- .../GeyserVelocityCommandExecutor.java | 13 ++-- .../command/VelocityCommandSender.java | 25 +++++-- .../geysermc/connector/GeyserConnector.java | 2 +- .../connector/command/CommandManager.java | 18 ++--- .../connector/command/CommandSender.java | 18 +++++ .../connector/command/GeyserCommand.java | 3 + .../command/defaults/DumpCommand.java | 16 ++--- .../command/defaults/HelpCommand.java | 14 ++-- .../command/defaults/ListCommand.java | 10 ++- .../command/defaults/OffhandCommand.java | 4 +- .../command/defaults/ReloadCommand.java | 9 +-- .../command/defaults/VersionCommand.java | 10 +-- .../org/geysermc/connector/entity/Entity.java | 2 +- .../network/UpstreamPacketHandler.java | 4 +- .../network/session/GeyserSession.java | 7 +- .../network/session/cache/BossBar.java | 4 +- .../EnchantmentInventoryTranslator.java | 4 +- .../translators/item/ItemTranslator.java | 2 +- .../translators/java/JavaChatTranslator.java | 2 +- .../java/JavaDisconnectPacket.java | 2 +- .../java/JavaJoinGameTranslator.java | 2 +- .../java/JavaLoginDisconnectTranslator.java | 2 +- .../translators/java/JavaTitleTranslator.java | 2 +- .../java/scoreboard/JavaTeamTranslator.java | 8 +-- .../java/window/JavaOpenWindowTranslator.java | 4 +- .../world/JavaNotifyClientTranslator.java | 2 +- .../java/world/JavaPlayEffectTranslator.java | 2 +- .../connector/utils/LanguageUtils.java | 4 +- .../geysermc/connector/utils/LocaleUtils.java | 1 - .../connector/utils/LoginEncryptionUtils.java | 6 +- .../connector/utils/MessageUtils.java | 2 +- .../connector/utils/SettingsUtils.java | 2 +- 37 files changed, 212 insertions(+), 120 deletions(-) diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/BungeeCommandSender.java b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/BungeeCommandSender.java index d40dc902..3ad8b54f 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/BungeeCommandSender.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/BungeeCommandSender.java @@ -25,17 +25,20 @@ package org.geysermc.platform.bungeecord.command; -import lombok.AllArgsConstructor; - import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.connection.ProxiedPlayer; - import org.geysermc.connector.command.CommandSender; +import org.geysermc.connector.utils.LanguageUtils; -@AllArgsConstructor public class BungeeCommandSender implements CommandSender { - private net.md_5.bungee.api.CommandSender handle; + private final net.md_5.bungee.api.CommandSender handle; + + public BungeeCommandSender(net.md_5.bungee.api.CommandSender handle) { + this.handle = handle; + // Ensure even Java players' languages are loaded + LanguageUtils.loadGeyserLocale(getLocale()); + } @Override public String getName() { @@ -51,4 +54,14 @@ public class BungeeCommandSender implements CommandSender { public boolean isConsole() { return !(handle instanceof ProxiedPlayer); } + + @Override + public String getLocale() { + if (handle instanceof ProxiedPlayer) { + ProxiedPlayer player = (ProxiedPlayer) handle; + String locale = player.getLocale().getLanguage() + "_" + player.getLocale().getCountry(); + return LanguageUtils.formatLocale(locale); + } + return LanguageUtils.getDefaultLocale(); + } } diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/GeyserBungeeCommandExecutor.java b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/GeyserBungeeCommandExecutor.java index f673a3f5..ff7a2e3d 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/GeyserBungeeCommandExecutor.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/GeyserBungeeCommandExecutor.java @@ -27,13 +27,10 @@ package org.geysermc.platform.bungeecord.command; import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.CommandSender; -import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.plugin.Command; import net.md_5.bungee.api.plugin.TabExecutor; - import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.GeyserCommand; -import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.utils.LanguageUtils; import java.util.ArrayList; @@ -41,7 +38,7 @@ import java.util.Arrays; public class GeyserBungeeCommandExecutor extends Command implements TabExecutor { - private GeyserConnector connector; + private final GeyserConnector connector; public GeyserBungeeCommandExecutor(GeyserConnector connector) { super("geyser"); @@ -54,14 +51,10 @@ public class GeyserBungeeCommandExecutor extends Command implements TabExecutor if (args.length > 0) { if (getCommand(args[0]) != null) { if (!sender.hasPermission(getCommand(args[0]).getPermission())) { - String message = ""; - if (sender instanceof GeyserSession) { - message = LanguageUtils.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", ((GeyserSession) sender).getClientData().getLanguageCode()); - } else { - message = LanguageUtils.getLocaleStringLog("geyser.bootstrap.command.permission_fail"); - } + BungeeCommandSender commandSender = new BungeeCommandSender(sender); + String message = LanguageUtils.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", commandSender.getLocale()); - sender.sendMessage(TextComponent.fromLegacyText(ChatColor.RED + message)); + commandSender.sendMessage(ChatColor.RED + message); return; } getCommand(args[0]).execute(new BungeeCommandSender(sender), args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]); diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java index 9cc0bc06..892f8feb 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java @@ -40,6 +40,7 @@ import org.geysermc.connector.utils.FileUtils; import org.geysermc.connector.utils.LanguageUtils; import org.geysermc.platform.spigot.command.GeyserSpigotCommandExecutor; import org.geysermc.platform.spigot.command.GeyserSpigotCommandManager; +import org.geysermc.platform.spigot.command.SpigotCommandSender; import org.geysermc.platform.spigot.world.GeyserSpigotBlockPlaceListener; import org.geysermc.platform.spigot.world.GeyserSpigotWorldManager; @@ -130,6 +131,9 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { geyserLogger.debug("Legacy version of Minecraft (1.15.2 or older) detected; not using 3D biomes."); } + // Set if we need to use a different method for getting a player's locale + SpigotCommandSender.setUseLegacyLocaleMethod(!isCompatible(Bukkit.getServer().getVersion(), "1.12.0")); + this.geyserWorldManager = new GeyserSpigotWorldManager(isLegacy, use3dBiomes, isViaVersion); GeyserSpigotBlockPlaceListener blockPlaceListener = new GeyserSpigotBlockPlaceListener(connector, isLegacy, isViaVersion); diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/GeyserSpigotCommandExecutor.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/GeyserSpigotCommandExecutor.java index 2dba2901..0edb8448 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/GeyserSpigotCommandExecutor.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/GeyserSpigotCommandExecutor.java @@ -32,7 +32,6 @@ import org.bukkit.command.CommandSender; import org.bukkit.command.TabExecutor; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.GeyserCommand; -import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.utils.LanguageUtils; import java.util.ArrayList; @@ -42,21 +41,17 @@ import java.util.List; @AllArgsConstructor public class GeyserSpigotCommandExecutor implements TabExecutor { - private GeyserConnector connector; + private final GeyserConnector connector; @Override public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { if (args.length > 0) { if (getCommand(args[0]) != null) { if (!sender.hasPermission(getCommand(args[0]).getPermission())) { - String message = ""; - if (sender instanceof GeyserSession) { - message = LanguageUtils.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", ((GeyserSession) sender).getClientData().getLanguageCode()); - } else { - message = LanguageUtils.getLocaleStringLog("geyser.bootstrap.command.permission_fail"); - } + SpigotCommandSender commandSender = new SpigotCommandSender(sender); + String message = LanguageUtils.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", commandSender.getLocale());; - sender.sendMessage(ChatColor.RED + message); + commandSender.sendMessage(ChatColor.RED + message); return true; } getCommand(args[0]).execute(new SpigotCommandSender(sender), args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]); diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/SpigotCommandSender.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/SpigotCommandSender.java index 55475a30..93a50066 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/SpigotCommandSender.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/SpigotCommandSender.java @@ -25,15 +25,33 @@ package org.geysermc.platform.spigot.command; -import lombok.AllArgsConstructor; - import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.entity.Player; +import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.CommandSender; +import org.geysermc.connector.utils.LanguageUtils; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; -@AllArgsConstructor public class SpigotCommandSender implements CommandSender { - private org.bukkit.command.CommandSender handle; + /** + * Whether to use {@code Player.getLocale()} or {@code Player.spigot().getLocale()}, depending on version. + * 1.12 or greater should not use the legacy method. + */ + private static boolean USE_LEGACY_METHOD = false; + private static Method LOCALE_METHOD; + + private final org.bukkit.command.CommandSender handle; + private final String locale; + + public SpigotCommandSender(org.bukkit.command.CommandSender handle) { + this.handle = handle; + this.locale = getSpigotLocale(); + // Ensure even Java players' languages are loaded + LanguageUtils.loadGeyserLocale(locale); + } @Override public String getName() { @@ -49,4 +67,49 @@ public class SpigotCommandSender implements CommandSender { public boolean isConsole() { return handle instanceof ConsoleCommandSender; } + + @Override + public String getLocale() { + return locale; + } + + /** + * Set if we are on pre-1.12, and therefore {@code player.getLocale()} doesn't exist and we have to get + * {@code player.spigot().getLocale()}. + * + * @param useLegacyMethod if we are running pre-1.12 and therefore need to use reflection to get the player locale + */ + public static void setUseLegacyLocaleMethod(boolean useLegacyMethod) { + USE_LEGACY_METHOD = useLegacyMethod; + if (USE_LEGACY_METHOD) { + try { + //noinspection JavaReflectionMemberAccess - of course it doesn't exist; that's why we're doing it + LOCALE_METHOD = Player.Spigot.class.getMethod("getLocale"); + } catch (NoSuchMethodException e) { + GeyserConnector.getInstance().getLogger().debug("Player.Spigot.getLocale() doesn't exist? Not a big deal but if you're seeing this please report it to the developers!"); + } + } + } + + /** + * So we only have to do nasty reflection stuff once per command + * + * @return the locale of the Spigot player + */ + private String getSpigotLocale() { + if (handle instanceof Player) { + Player player = (Player) handle; + if (USE_LEGACY_METHOD) { + try { + // sigh + // This was the only option on older Spigot instances and now it's gone + return (String) LOCALE_METHOD.invoke(player.spigot()); + } catch (IllegalAccessException | InvocationTargetException ignored) { + } + } else { + return player.getLocale(); + } + } + return LanguageUtils.getDefaultLocale(); + } } diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandExecutor.java b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandExecutor.java index afd6c3bf..c329fb19 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandExecutor.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandExecutor.java @@ -27,14 +27,11 @@ package org.geysermc.platform.velocity.command; import com.velocitypowered.api.command.Command; import com.velocitypowered.api.command.CommandSource; - import lombok.AllArgsConstructor; - -import net.kyori.text.TextComponent; - -import org.geysermc.connector.common.ChatColor; import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.command.CommandSender; import org.geysermc.connector.command.GeyserCommand; +import org.geysermc.connector.common.ChatColor; import org.geysermc.connector.utils.LanguageUtils; import java.util.Arrays; @@ -42,15 +39,15 @@ import java.util.Arrays; @AllArgsConstructor public class GeyserVelocityCommandExecutor implements Command { - private GeyserConnector connector; + private final GeyserConnector connector; @Override public void execute(CommandSource source, String[] args) { if (args.length > 0) { if (getCommand(args[0]) != null) { if (!source.hasPermission(getCommand(args[0]).getPermission())) { - // Not ideal to use log here but we dont get a session - source.sendMessage(TextComponent.of(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.bootstrap.command.permission_fail"))); + CommandSender sender = new VelocityCommandSender(source); + sender.sendMessage(ChatColor.RED + LanguageUtils.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.getLocale())); return; } getCommand(args[0]).execute(new VelocityCommandSender(source), args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]); diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/VelocityCommandSender.java b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/VelocityCommandSender.java index 1b0d6f3e..3a1c4603 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/VelocityCommandSender.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/VelocityCommandSender.java @@ -28,17 +28,21 @@ package org.geysermc.platform.velocity.command; import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.proxy.ConsoleCommandSource; import com.velocitypowered.api.proxy.Player; - -import lombok.AllArgsConstructor; - import net.kyori.text.TextComponent; - import org.geysermc.connector.command.CommandSender; +import org.geysermc.connector.utils.LanguageUtils; + +import java.util.Locale; -@AllArgsConstructor public class VelocityCommandSender implements CommandSender { - private CommandSource handle; + private final CommandSource handle; + + public VelocityCommandSender(CommandSource handle) { + this.handle = handle; + // Ensure even Java players' languages are loaded + LanguageUtils.loadGeyserLocale(getLocale()); + } @Override public String getName() { @@ -59,4 +63,13 @@ public class VelocityCommandSender implements CommandSender { public boolean isConsole() { return handle instanceof ConsoleCommandSource; } + + @Override + public String getLocale() { + if (handle instanceof Player) { + Locale locale = ((Player) handle).getPlayerSettings().getLocale(); + return LanguageUtils.formatLocale(locale.getLanguage() + "_" + locale.getCountry()); + } + return LanguageUtils.getDefaultLocale(); + } } diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index afed4dfd..1d535f54 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -271,7 +271,7 @@ public class GeyserConnector { // Make a copy to prevent ConcurrentModificationException final List tmpPlayers = new ArrayList<>(players); for (GeyserSession playerSession : tmpPlayers) { - playerSession.disconnect(LanguageUtils.getPlayerLocaleString("geyser.core.shutdown.kick.message", playerSession.getClientData().getLanguageCode())); + playerSession.disconnect(LanguageUtils.getPlayerLocaleString("geyser.core.shutdown.kick.message", playerSession.getLocale())); } CompletableFuture future = CompletableFuture.runAsync(new Runnable() { diff --git a/connector/src/main/java/org/geysermc/connector/command/CommandManager.java b/connector/src/main/java/org/geysermc/connector/command/CommandManager.java index 2b35424a..7adce430 100644 --- a/connector/src/main/java/org/geysermc/connector/command/CommandManager.java +++ b/connector/src/main/java/org/geysermc/connector/command/CommandManager.java @@ -40,19 +40,19 @@ public abstract class CommandManager { @Getter private final Map commands = Collections.synchronizedMap(new HashMap<>()); - private GeyserConnector connector; + private final GeyserConnector connector; public CommandManager(GeyserConnector connector) { this.connector = connector; - registerCommand(new HelpCommand(connector, "help", LanguageUtils.getLocaleStringLog("geyser.commands.help.desc"), "geyser.command.help")); - registerCommand(new ListCommand(connector, "list", LanguageUtils.getLocaleStringLog("geyser.commands.list.desc"), "geyser.command.list")); - registerCommand(new ReloadCommand(connector, "reload", LanguageUtils.getLocaleStringLog("geyser.commands.reload.desc"), "geyser.command.reload")); - registerCommand(new StopCommand(connector, "stop", LanguageUtils.getLocaleStringLog("geyser.commands.stop.desc"), "geyser.command.stop")); - registerCommand(new OffhandCommand(connector, "offhand", LanguageUtils.getLocaleStringLog("geyser.commands.offhand.desc"), "geyser.command.offhand")); - registerCommand(new DumpCommand(connector, "dump", LanguageUtils.getLocaleStringLog("geyser.commands.dump.desc"), "geyser.command.dump")); - registerCommand(new VersionCommand(connector, "version", LanguageUtils.getLocaleStringLog("geyser.commands.version.desc"), "geyser.command.version")); - registerCommand(new StatisticsCommand(connector, "statistics", LanguageUtils.getLocaleStringLog("geyser.commands.statistics.desc"), "geyser.command.statistics")); + registerCommand(new HelpCommand(connector, "help", "geyser.commands.help.desc", "geyser.command.help")); + registerCommand(new ListCommand(connector, "list", "geyser.commands.list.desc", "geyser.command.list")); + registerCommand(new ReloadCommand(connector, "reload", "geyser.commands.reload.desc", "geyser.command.reload")); + registerCommand(new StopCommand(connector, "stop", "geyser.commands.stop.desc", "geyser.command.stop")); + registerCommand(new OffhandCommand(connector, "offhand", "geyser.commands.offhand.desc", "geyser.command.offhand")); + registerCommand(new DumpCommand(connector, "dump", "geyser.commands.dump.desc", "geyser.command.dump")); + registerCommand(new VersionCommand(connector, "version", "geyser.commands.version.desc", "geyser.command.version")); + registerCommand(new StatisticsCommand(connector, "statistics", "geyser.commands.statistics.desc", "geyser.command.statistics")); } public void registerCommand(GeyserCommand command) { diff --git a/connector/src/main/java/org/geysermc/connector/command/CommandSender.java b/connector/src/main/java/org/geysermc/connector/command/CommandSender.java index be9e3430..9c398a2a 100644 --- a/connector/src/main/java/org/geysermc/connector/command/CommandSender.java +++ b/connector/src/main/java/org/geysermc/connector/command/CommandSender.java @@ -25,6 +25,12 @@ package org.geysermc.connector.command; +import org.geysermc.connector.utils.LanguageUtils; + +/** + * Implemented on top of any class that can send a command. + * For example, it wraps around Spigot's CommandSender class. + */ public interface CommandSender { String getName(); @@ -37,5 +43,17 @@ public interface CommandSender { void sendMessage(String message); + /** + * @return true if the specified sender is from the console. + */ boolean isConsole(); + + /** + * Returns the locale of the command sender. Defaults to the default locale at {@link LanguageUtils#getDefaultLocale()}. + * + * @return the locale of the command sender. + */ + default String getLocale() { + return LanguageUtils.getDefaultLocale(); + } } diff --git a/connector/src/main/java/org/geysermc/connector/command/GeyserCommand.java b/connector/src/main/java/org/geysermc/connector/command/GeyserCommand.java index 2bb8893c..adbebd0c 100644 --- a/connector/src/main/java/org/geysermc/connector/command/GeyserCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/GeyserCommand.java @@ -38,6 +38,9 @@ import java.util.List; public abstract class GeyserCommand { protected final String name; + /** + * The description of the command - will attempt to be translated. + */ protected final String description; protected final String permission; diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/DumpCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/DumpCommand.java index 9103755a..f2c37da2 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/DumpCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/DumpCommand.java @@ -73,7 +73,7 @@ public class DumpCommand extends GeyserCommand { AsteriskSerializer.showSensitive = showSensitive; - sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.dump.collecting")); + sender.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.commands.dump.collecting", sender.getLocale())); String dumpData = ""; try { if (offlineDump) { @@ -82,7 +82,7 @@ public class DumpCommand extends GeyserCommand { dumpData = MAPPER.writeValueAsString(new DumpInfo()); } } catch (IOException e) { - sender.sendMessage(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.commands.dump.collect_error")); + sender.sendMessage(ChatColor.RED + LanguageUtils.getPlayerLocaleString("geyser.commands.dump.collect_error", sender.getLocale())); connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.commands.dump.collect_error_short"), e); return; } @@ -90,21 +90,21 @@ public class DumpCommand extends GeyserCommand { String uploadedDumpUrl = ""; if (offlineDump) { - sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.dump.writing")); + sender.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.commands.dump.writing", sender.getLocale())); try { FileOutputStream outputStream = new FileOutputStream(GeyserConnector.getInstance().getBootstrap().getConfigFolder().resolve("dump.json").toFile()); outputStream.write(dumpData.getBytes()); outputStream.close(); } catch (IOException e) { - sender.sendMessage(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.commands.dump.write_error")); + sender.sendMessage(ChatColor.RED + LanguageUtils.getPlayerLocaleString("geyser.commands.dump.write_error", sender.getLocale())); connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.commands.dump.write_error_short"), e); return; } uploadedDumpUrl = "dump.json"; } else { - sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.dump.uploading")); + sender.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.commands.dump.uploading", sender.getLocale())); String response; JsonNode responseNode; @@ -112,20 +112,20 @@ public class DumpCommand extends GeyserCommand { response = WebUtils.post(DUMP_URL + "documents", dumpData); responseNode = MAPPER.readTree(response); } catch (IOException e) { - sender.sendMessage(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.commands.dump.upload_error")); + sender.sendMessage(ChatColor.RED + LanguageUtils.getPlayerLocaleString("geyser.commands.dump.upload_error", sender.getLocale())); connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.commands.dump.upload_error_short"), e); return; } if (!responseNode.has("key")) { - sender.sendMessage(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.commands.dump.upload_error_short") + ": " + (responseNode.has("message") ? responseNode.get("message").asText() : response)); + sender.sendMessage(ChatColor.RED + LanguageUtils.getPlayerLocaleString("geyser.commands.dump.upload_error_short", sender.getLocale()) + ": " + (responseNode.has("message") ? responseNode.get("message").asText() : response)); return; } uploadedDumpUrl = DUMP_URL + responseNode.get("key").asText(); } - sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.dump.message") + " " + ChatColor.DARK_AQUA + uploadedDumpUrl); + sender.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.commands.dump.message", sender.getLocale()) + " " + ChatColor.DARK_AQUA + uploadedDumpUrl); if (!sender.isConsole()) { connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.commands.dump.created", sender.getName(), uploadedDumpUrl)); } diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/HelpCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/HelpCommand.java index 0407cf6e..268dc4b5 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/HelpCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/HelpCommand.java @@ -25,11 +25,10 @@ package org.geysermc.connector.command.defaults; -import org.geysermc.connector.common.ChatColor; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.CommandSender; import org.geysermc.connector.command.GeyserCommand; -import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.common.ChatColor; import org.geysermc.connector.utils.LanguageUtils; import java.util.Collections; @@ -52,17 +51,12 @@ public class HelpCommand extends GeyserCommand { public void execute(CommandSender sender, String[] args) { int page = 1; int maxPage = 1; - String header = ""; - - if (sender instanceof GeyserSession) { - header = LanguageUtils.getPlayerLocaleString("geyser.commands.help.header", ((GeyserSession) sender).getClientData().getLanguageCode(), page, maxPage); - } else { - header = LanguageUtils.getLocaleStringLog("geyser.commands.help.header", page, maxPage); - } + String header = LanguageUtils.getPlayerLocaleString("geyser.commands.help.header", sender.getLocale(), page, maxPage); sender.sendMessage(header); Map cmds = connector.getCommandManager().getCommands(); List commands = connector.getCommandManager().getCommands().keySet().stream().sorted().collect(Collectors.toList()); - commands.forEach(cmd -> sender.sendMessage(ChatColor.YELLOW + "/geyser " + cmd + ChatColor.WHITE + ": " + cmds.get(cmd).getDescription())); + commands.forEach(cmd -> sender.sendMessage(ChatColor.YELLOW + "/geyser " + cmd + ChatColor.WHITE + ": " + + LanguageUtils.getPlayerLocaleString(cmds.get(cmd).getDescription(), sender.getLocale()))); } } diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/ListCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/ListCommand.java index 3c78c554..255c6a9b 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/ListCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/ListCommand.java @@ -35,7 +35,7 @@ import java.util.stream.Collectors; public class ListCommand extends GeyserCommand { - private GeyserConnector connector; + private final GeyserConnector connector; public ListCommand(GeyserConnector connector, String name, String description, String permission) { super(name, description, permission); @@ -46,11 +46,9 @@ public class ListCommand extends GeyserCommand { @Override public void execute(CommandSender sender, String[] args) { String message = ""; - if (sender instanceof GeyserSession) { - message = LanguageUtils.getPlayerLocaleString("geyser.commands.list.message", ((GeyserSession) sender).getClientData().getLanguageCode(), connector.getPlayers().size(), connector.getPlayers().stream().map(GeyserSession::getName).collect(Collectors.joining(" "))); - } else { - message = LanguageUtils.getLocaleStringLog("geyser.commands.list.message", connector.getPlayers().size(), connector.getPlayers().stream().map(GeyserSession::getName).collect(Collectors.joining(" "))); - } + message = LanguageUtils.getPlayerLocaleString("geyser.commands.list.message", sender.getLocale(), + connector.getPlayers().size(), + connector.getPlayers().stream().map(GeyserSession::getName).collect(Collectors.joining(" "))); sender.sendMessage(message); } diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/OffhandCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/OffhandCommand.java index d5e0a792..4b6397f1 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/OffhandCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/OffhandCommand.java @@ -36,7 +36,7 @@ import org.geysermc.connector.network.session.GeyserSession; public class OffhandCommand extends GeyserCommand { - private GeyserConnector connector; + private final GeyserConnector connector; public OffhandCommand(GeyserConnector connector, String name, String description, String permission) { super(name, description, permission); @@ -58,7 +58,7 @@ public class OffhandCommand extends GeyserCommand { session.sendDownstreamPacket(releaseItemPacket); return; } - // Needed for Bukkit - sender is not an instance of GeyserSession + // Needed for Spigot - sender is not an instance of GeyserSession for (GeyserSession session : connector.getPlayers()) { if (sender.getName().equals(session.getPlayerEntity().getUsername())) { ClientPlayerActionPacket releaseItemPacket = new ClientPlayerActionPacket(PlayerAction.SWAP_HANDS, new Position(0,0,0), diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/ReloadCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/ReloadCommand.java index 9c24fa2b..6b2be294 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/ReloadCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/ReloadCommand.java @@ -47,17 +47,12 @@ public class ReloadCommand extends GeyserCommand { return; } - String message = ""; - if (sender instanceof GeyserSession) { - message = LanguageUtils.getPlayerLocaleString("geyser.commands.reload.message", ((GeyserSession) sender).getClientData().getLanguageCode()); - } else { - message = LanguageUtils.getLocaleStringLog("geyser.commands.reload.message"); - } + String message = LanguageUtils.getPlayerLocaleString("geyser.commands.reload.message", sender.getLocale()); sender.sendMessage(message); for (GeyserSession session : connector.getPlayers()) { - session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.commands.reload.kick", session.getClientData().getLanguageCode())); + session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.commands.reload.kick", session.getLocale())); } connector.reload(); } diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/VersionCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/VersionCommand.java index f7f62e59..562bc9fb 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/VersionCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/VersionCommand.java @@ -61,12 +61,12 @@ public class VersionCommand extends GeyserCommand { bedrockVersions = BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion(); } - sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.version.version", GeyserConnector.NAME, GeyserConnector.VERSION, MinecraftConstants.GAME_VERSION, bedrockVersions)); + sender.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.commands.version.version", sender.getLocale(), GeyserConnector.NAME, GeyserConnector.VERSION, MinecraftConstants.GAME_VERSION, bedrockVersions)); // Disable update checking in dev mode //noinspection ConstantConditions - changes in production if (!GeyserConnector.VERSION.equals("DEV")) { - sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.version.checking")); + sender.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.commands.version.checking", sender.getLocale())); try { Properties gitProp = new Properties(); gitProp.load(FileUtils.getResource("git.properties")); @@ -76,16 +76,16 @@ public class VersionCommand extends GeyserCommand { int latestBuildNum = Integer.parseInt(buildXML.replaceAll("<(\\\\)?(/)?buildNumber>", "").trim()); int buildNum = Integer.parseInt(gitProp.getProperty("git.build.number")); if (latestBuildNum == buildNum) { - sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.version.no_updates")); + sender.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.commands.version.no_updates", sender.getLocale())); } else { - sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.version.outdated", (latestBuildNum - buildNum), "http://ci.geysermc.org/")); + sender.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.commands.version.outdated", sender.getLocale(), (latestBuildNum - buildNum), "https://ci.geysermc.org/")); } } else { throw new AssertionError("buildNumber missing"); } } catch (IOException | AssertionError | NumberFormatException e) { GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.commands.version.failed"), e); - sender.sendMessage(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.commands.version.failed")); + sender.sendMessage(ChatColor.RED + LanguageUtils.getPlayerLocaleString("geyser.commands.version.failed", sender.getLocale())); } } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/Entity.java b/connector/src/main/java/org/geysermc/connector/entity/Entity.java index 5e825e89..2dfb0c04 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/Entity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/Entity.java @@ -321,7 +321,7 @@ public class Entity { Message message = (Message) entityMetadata.getValue(); if (message != null) // Always translate even if it's a TextMessage since there could be translatable parameters - metadata.put(EntityData.NAMETAG, MessageUtils.getTranslatedBedrockMessage(message, session.getClientData().getLanguageCode(), true)); + metadata.put(EntityData.NAMETAG, MessageUtils.getTranslatedBedrockMessage(message, session.getLocale(), true)); } break; case 3: // is custom name visible diff --git a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java index f99abbe5..e871b7f7 100644 --- a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java @@ -170,7 +170,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { @Override public boolean handle(SetLocalPlayerAsInitializedPacket packet) { - LanguageUtils.loadGeyserLocale(session.getClientData().getLanguageCode()); + LanguageUtils.loadGeyserLocale(session.getLocale()); if (!session.isLoggedIn() && !session.isLoggingIn() && session.getConnector().getAuthType() == AuthType.ONLINE) { // TODO it is safer to key authentication on something that won't change (UUID, not username) @@ -185,7 +185,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { @Override public boolean handle(MovePlayerPacket packet) { if (session.isLoggingIn()) { - session.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.auth.login.wait", session.getClientData().getLanguageCode())); + session.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.auth.login.wait", session.getLocale())); } return translateAndDefault(packet); diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index a8a4adb9..ac72d219 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -467,7 +467,7 @@ public class GeyserSession implements CommandSender { // as it has to be extracted from a JAR if (locale.toLowerCase().equals("en_us") && !LocaleUtils.LOCALE_MAPPINGS.containsKey("en_us")) { // This should probably be left hardcoded as it will only show for en_us clients - sendMessage("Downloading your locale (en_us) this may take some time"); + sendMessage("Loading your locale (en_us); if this isn't already downloaded, this may take some time"); } // Download and load the language for the player @@ -587,6 +587,11 @@ public class GeyserSession implements CommandSender { return false; } + @Override + public String getLocale() { + return clientData.getLanguageCode(); + } + public void sendForm(FormWindow window, int id) { windowCache.showWindow(window, id); } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/BossBar.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/BossBar.java index 68e8519c..fdc609ab 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/BossBar.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/BossBar.java @@ -58,7 +58,7 @@ public class BossBar { BossEventPacket bossEventPacket = new BossEventPacket(); bossEventPacket.setBossUniqueEntityId(entityId); bossEventPacket.setAction(BossEventPacket.Action.CREATE); - bossEventPacket.setTitle(MessageUtils.getTranslatedBedrockMessage(title, session.getClientData().getLanguageCode())); + bossEventPacket.setTitle(MessageUtils.getTranslatedBedrockMessage(title, session.getLocale())); bossEventPacket.setHealthPercentage(health); bossEventPacket.setColor(color); //ignored by client bossEventPacket.setOverlay(overlay); @@ -72,7 +72,7 @@ public class BossBar { BossEventPacket bossEventPacket = new BossEventPacket(); bossEventPacket.setBossUniqueEntityId(entityId); bossEventPacket.setAction(BossEventPacket.Action.UPDATE_NAME); - bossEventPacket.setTitle(MessageUtils.getTranslatedBedrockMessage(title, session.getClientData().getLanguageCode())); + bossEventPacket.setTitle(MessageUtils.getTranslatedBedrockMessage(title, session.getLocale())); session.sendUpstreamPacket(bossEventPacket); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/EnchantmentInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/EnchantmentInventoryTranslator.java index cbcdce10..8aa91b36 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/EnchantmentInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/EnchantmentInventoryTranslator.java @@ -199,7 +199,7 @@ public class EnchantmentInventoryTranslator extends BlockInventoryTranslator { private String toRomanNumeral(GeyserSession session, int level) { return LocaleUtils.getLocaleString("enchantment.level." + level, - session.getClientData().getLanguageCode()); + session.getLocale()); } /** @@ -261,7 +261,7 @@ public class EnchantmentInventoryTranslator extends BlockInventoryTranslator { public String toString(GeyserSession session) { return LocaleUtils.getLocaleString("enchantment.minecraft." + this.toString().toLowerCase(), - session.getClientData().getLanguageCode()); + session.getLocale()); } } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java index ef629494..f95a0ccc 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java @@ -400,7 +400,7 @@ public abstract class ItemTranslator { // Check if its a message to translate if (MessageUtils.isMessage(name)) { // Get the translated name - name = MessageUtils.getTranslatedBedrockMessage(MessageSerializer.fromString(name), session.getClientData().getLanguageCode()); + name = MessageUtils.getTranslatedBedrockMessage(MessageSerializer.fromString(name), session.getLocale()); // Add the new name tag display.put(new StringTag("Name", name)); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaChatTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaChatTranslator.java index d222d729..186aaf66 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaChatTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaChatTranslator.java @@ -59,7 +59,7 @@ public class JavaChatTranslator extends PacketTranslator { break; } - String locale = session.getClientData().getLanguageCode(); + String locale = session.getLocale(); if (packet.getMessage() instanceof TranslationMessage) { textPacket.setType(TextPacket.Type.TRANSLATION); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDisconnectPacket.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDisconnectPacket.java index 43211111..f36da367 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDisconnectPacket.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDisconnectPacket.java @@ -36,6 +36,6 @@ public class JavaDisconnectPacket extends PacketTranslator skinParts = Arrays.asList(SkinPart.values()); ClientSettingsPacket clientSettingsPacket = new ClientSettingsPacket(locale, (byte) session.getRenderDistance(), ChatVisibility.FULL, true, skinParts, HandPreference.RIGHT_HAND); session.sendDownstreamPacket(clientSettingsPacket); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginDisconnectTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginDisconnectTranslator.java index d4504226..e7486c99 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginDisconnectTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginDisconnectTranslator.java @@ -37,6 +37,6 @@ public class JavaLoginDisconnectTranslator extends PacketTranslator { @Override public void translate(ServerTitlePacket packet, GeyserSession session) { SetTitlePacket titlePacket = new SetTitlePacket(); - String locale = session.getClientData().getLanguageCode(); + String locale = session.getLocale(); switch (packet.getAction()) { case TITLE: diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaTeamTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaTeamTranslator.java index 998e5eff..c621fc1f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaTeamTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaTeamTranslator.java @@ -62,8 +62,8 @@ public class JavaTeamTranslator extends PacketTranslator { .setName(MessageUtils.getBedrockMessage(packet.getDisplayName())) .setColor(packet.getColor()) .setNameTagVisibility(packet.getNameTagVisibility()) - .setPrefix(MessageUtils.getTranslatedBedrockMessage(packet.getPrefix(), session.getClientData().getLanguageCode())) - .setSuffix(MessageUtils.getTranslatedBedrockMessage(packet.getSuffix(), session.getClientData().getLanguageCode())); + .setPrefix(MessageUtils.getTranslatedBedrockMessage(packet.getPrefix(), session.getLocale())) + .setSuffix(MessageUtils.getTranslatedBedrockMessage(packet.getSuffix(), session.getLocale())); break; case UPDATE: if (team == null) { @@ -77,8 +77,8 @@ public class JavaTeamTranslator extends PacketTranslator { team.setName(MessageUtils.getBedrockMessage(packet.getDisplayName())) .setColor(packet.getColor()) .setNameTagVisibility(packet.getNameTagVisibility()) - .setPrefix(MessageUtils.getTranslatedBedrockMessage(packet.getPrefix(), session.getClientData().getLanguageCode())) - .setSuffix(MessageUtils.getTranslatedBedrockMessage(packet.getSuffix(), session.getClientData().getLanguageCode())) + .setPrefix(MessageUtils.getTranslatedBedrockMessage(packet.getPrefix(), session.getLocale())) + .setSuffix(MessageUtils.getTranslatedBedrockMessage(packet.getSuffix(), session.getLocale())) .setUpdateType(UpdateType.UPDATE); break; case ADD_PLAYER: diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java index 099de317..2c10ded6 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java @@ -58,9 +58,9 @@ public class JavaOpenWindowTranslator extends PacketTranslator params = new ArrayList<>(); String recordString = "%item." + soundEvent.name().toLowerCase(Locale.ROOT) + ".desc"; - params.add(LocaleUtils.getLocaleString(recordString, session.getClientData().getLanguageCode())); + params.add(LocaleUtils.getLocaleString(recordString, session.getLocale())); textPacket.setParameters(params); session.sendUpstreamPacket(textPacket); } diff --git a/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java index de6796a2..06d2936e 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java @@ -59,6 +59,8 @@ public class LanguageUtils { */ public static void loadGeyserLocale(String locale) { locale = formatLocale(locale); + // Don't load the locale if it's already loaded. + if (LOCALE_MAPPINGS.containsKey(locale)) return; InputStream localeStream = GeyserConnector.class.getClassLoader().getResourceAsStream("languages/texts/" + locale + ".properties"); @@ -131,7 +133,7 @@ public class LanguageUtils { * @param locale The locale to format * @return The formatted locale */ - private static String formatLocale(String locale) { + public static String formatLocale(String locale) { try { String[] parts = locale.toLowerCase().split("_"); return parts[0] + "_" + parts[1].toUpperCase(); diff --git a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java index d1d59490..11e81eee 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java @@ -35,7 +35,6 @@ import org.geysermc.connector.GeyserConnector; import java.io.*; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.HashMap; import java.util.Iterator; import java.util.List; diff --git a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java index 4bc997bd..fe63bc91 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java @@ -160,7 +160,7 @@ public class LoginEncryptionUtils { private static int AUTH_DETAILS_FORM_ID = 1337; public static void showLoginWindow(GeyserSession session) { - String userLanguage = session.getClientData().getLanguageCode(); + String userLanguage = session.getLocale(); SimpleFormWindow window = new SimpleFormWindow(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.title", userLanguage), LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.desc", userLanguage)); window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.btn_login", userLanguage))); window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.btn_disconnect", userLanguage))); @@ -169,7 +169,7 @@ public class LoginEncryptionUtils { } public static void showLoginDetailsWindow(GeyserSession session) { - String userLanguage = session.getClientData().getLanguageCode(); + String userLanguage = session.getLocale(); CustomFormWindow window = new CustomFormBuilder(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.details.title", userLanguage)) .addComponent(new LabelComponent(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.details.desc", userLanguage))) .addComponent(new InputComponent(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.details.email", userLanguage), "account@geysermc.org", "")) @@ -210,7 +210,7 @@ public class LoginEncryptionUtils { if (response.getClickedButtonId() == 0) { showLoginDetailsWindow(session); } else if(response.getClickedButtonId() == 1) { - session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.getClientData().getLanguageCode())); + session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.getLocale())); } } else { showLoginWindow(session); diff --git a/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java b/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java index a127fd8d..b5a2bfdc 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java @@ -485,7 +485,7 @@ public class MessageUtils { */ public static boolean isTooLong(String message, GeyserSession session) { if (message.length() > 256) { - session.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.chat.too_long", session.getClientData().getLanguageCode(), message.length())); + session.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.chat.too_long", session.getLocale(), message.length())); return true; } diff --git a/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java b/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java index 13db4682..eaf71058 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java @@ -52,7 +52,7 @@ public class SettingsUtils { */ public static void buildForm(GeyserSession session) { // Cache the language for cleaner access - String language = session.getClientData().getLanguageCode(); + String language = session.getLocale(); CustomFormBuilder builder = new CustomFormBuilder(LanguageUtils.getPlayerLocaleString("geyser.settings.title.main", language)); builder.setIcon(new FormImage(FormImage.FormImageType.PATH, "textures/ui/settings_glyph_color_2x.png")); From 8fdaf6a3850b2590ce930e476d799e0e12f190af Mon Sep 17 00:00:00 2001 From: David Choo Date: Thu, 29 Oct 2020 18:35:46 -0400 Subject: [PATCH 018/116] Ender dragon Melee Attacks (#1466) * Create and position Ender Dragon Bounding Box Currently allows the player to "kill aura" target the ender dragon. * Use an entity to handle attacks for each hitbox * Use the proper flag to make entities invisible * Clean up and add some comments * Ender dragon entity metadata improvements * Add doc to segment functions * Add changes Co-authored-by: DoctorMacc --- .../living/monster/EnderDragonEntity.java | 163 ++++++++++++++++-- .../living/monster/EnderDragonPartEntity.java | 42 +++++ .../connector/entity/type/EntityType.java | 9 +- ...BedrockInventoryTransactionTranslator.java | 15 +- 4 files changed, 212 insertions(+), 17 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonPartEntity.java diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonEntity.java index aa2b4e02..14466dda 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonEntity.java @@ -32,33 +32,60 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.packet.AddEntityPacket; import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; +import lombok.Data; import org.geysermc.connector.entity.living.InsentientEntity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + public class EnderDragonEntity extends InsentientEntity { + /** + * The Ender Dragon has multiple hit boxes, which + * are each its own invisible entity + */ + private EnderDragonPartEntity head; + private EnderDragonPartEntity neck; + private EnderDragonPartEntity body; + private EnderDragonPartEntity leftWing; + private EnderDragonPartEntity rightWing; + private EnderDragonPartEntity[] tail; + + private EnderDragonPartEntity[] allParts; + + /** + * A circular buffer that stores a history of + * y and yaw values. + */ + private final Segment[] segmentHistory = new Segment[19]; + private int latestSegment = -1; + + private boolean hovering; + + private ScheduledFuture partPositionUpdater; public EnderDragonEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); + + metadata.getFlags().setFlag(EntityFlag.FIRE_IMMUNE, true); } @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + // Phase if (entityMetadata.getId() == 15) { - metadata.getFlags().setFlag(EntityFlag.FIRE_IMMUNE, true); - switch ((int) entityMetadata.getValue()) { + int value = (int) entityMetadata.getValue(); + if (value == 5) { // Performing breath attack - case 5: - EntityEventPacket entityEventPacket = new EntityEventPacket(); - entityEventPacket.setType(EntityEventType.DRAGON_FLAMING); - entityEventPacket.setRuntimeEntityId(geyserId); - entityEventPacket.setData(0); - session.sendUpstreamPacket(entityEventPacket); - case 6: - case 7: - metadata.getFlags().setFlag(EntityFlag.SITTING, true); - break; + EntityEventPacket entityEventPacket = new EntityEventPacket(); + entityEventPacket.setType(EntityEventType.DRAGON_FLAMING); + entityEventPacket.setRuntimeEntityId(geyserId); + entityEventPacket.setData(0); + session.sendUpstreamPacket(entityEventPacket); } + metadata.getFlags().setFlag(EntityFlag.SITTING, value == 5 || value == 6 || value == 7); + hovering = value == 10; } super.updateBedrockMetadata(entityMetadata, session); } @@ -81,6 +108,118 @@ public class EnderDragonEntity extends InsentientEntity { valid = true; session.sendUpstreamPacket(addEntityPacket); + head = new EnderDragonPartEntity(entityId + 1, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 1, 1); + neck = new EnderDragonPartEntity(entityId + 2, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 3, 3); + body = new EnderDragonPartEntity(entityId + 3, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 5, 3); + leftWing = new EnderDragonPartEntity(entityId + 4, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 4, 2); + rightWing = new EnderDragonPartEntity(entityId + 5, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 4, 2); + tail = new EnderDragonPartEntity[3]; + for (int i = 0; i < 3; i++) { + tail[i] = new EnderDragonPartEntity(entityId + 6 + i, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 2, 2); + } + + allParts = new EnderDragonPartEntity[]{head, neck, body, leftWing, rightWing, tail[0], tail[1], tail[2]}; + + for (EnderDragonPartEntity part : allParts) { + session.getEntityCache().spawnEntity(part); + } + + for (int i = 0; i < segmentHistory.length; i++) { + segmentHistory[i] = new Segment(); + segmentHistory[i].yaw = rotation.getZ(); + segmentHistory[i].y = position.getY(); + } + + partPositionUpdater = session.getConnector().getGeneralThreadPool().scheduleAtFixedRate(() -> { + pushSegment(); + updateBoundingBoxes(session); + }, 0, 50, TimeUnit.MILLISECONDS); + session.getConnector().getLogger().debug("Spawned entity " + entityType + " at location " + position + " with id " + geyserId + " (java id " + entityId + ")"); } + + @Override + public boolean despawnEntity(GeyserSession session) { + partPositionUpdater.cancel(true); + + for (EnderDragonPartEntity part : allParts) { + part.despawnEntity(session); + } + return super.despawnEntity(session); + } + + /** + * Updates the positions of the Ender Dragon's multiple bounding boxes + * + * @param session GeyserSession. + */ + private void updateBoundingBoxes(GeyserSession session) { + Vector3f facingDir = Vector3f.createDirectionDeg(0, rotation.getZ()); + 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())); + float pitchXZ = (float) Math.cos(pitch); + float pitchY = (float) Math.sin(pitch); + + // Lowers the head when the dragon sits/hovers + float headDuck; + if (hovering || metadata.getFlags().getFlag(EntityFlag.SITTING)) { + headDuck = -1f; + } else { + headDuck = baseSegment.y - getSegment(0).y; + } + + head.setPosition(facingDir.up(pitchY).mul(pitchXZ, 1, -pitchXZ).mul(6.5f).up(headDuck)); + 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 - rotation.getZ()).mul(4.5f).up(2f); + rightWing.setPosition(wingPos); + leftWing.setPosition(wingPos.mul(-1, 1, -1)); // Mirror horizontally + + Vector3f tailBase = facingDir.mul(1.5f); + for (int i = 0; i < tail.length; i++) { + float distance = (i + 1) * 2f; + // Curls the tail when the dragon turns + Segment targetSegment = getSegment(12 + 2 * i); + float angle = rotation.getZ() + 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)); + } + // Send updated positions + for (EnderDragonPartEntity part : allParts) { + part.moveAbsolute(session, part.getPosition().add(position), Vector3f.ZERO, false, false); + } + } + + /** + * Store the current yaw and y into the circular buffer + */ + private void pushSegment() { + latestSegment = (latestSegment + 1) % segmentHistory.length; + segmentHistory[latestSegment].yaw = rotation.getZ(); + segmentHistory[latestSegment].y = position.getY(); + } + + /** + * Gets the previous yaw and y + * Used to curl the tail and pitch the head and tail up/down + * + * @param index Number of ticks in the past + * @return Segment with the yaw and y + */ + private Segment getSegment(int index) { + index = (latestSegment - index) % segmentHistory.length; + if (index < 0) { + index += segmentHistory.length; + } + return segmentHistory[index]; + } + + @Data + private static class Segment { + private float yaw; + private float y; + } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonPartEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonPartEntity.java new file mode 100644 index 00000000..bb5876ce --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonPartEntity.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2019-2020 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.connector.entity.living.monster; + +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.connector.entity.Entity; +import org.geysermc.connector.entity.type.EntityType; + +public class EnderDragonPartEntity extends Entity { + public EnderDragonPartEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation, float width, float height) { + super(entityId, geyserId, entityType, position, motion, rotation); + + metadata.put(EntityData.BOUNDING_BOX_WIDTH, width); + metadata.put(EntityData.BOUNDING_BOX_HEIGHT, height); + metadata.getFlags().setFlag(EntityFlag.INVISIBLE, true); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java index 24c15018..fddab5a4 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java +++ b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java @@ -86,7 +86,7 @@ public enum EntityType { ELDER_GUARDIAN(ElderGuardianEntity.class, 50, 1.9975f), NPC(PlayerEntity.class, 51, 1.8f, 0.6f, 0.6f, 1.62f), WITHER(WitherEntity.class, 52, 3.5f, 0.9f), - ENDER_DRAGON(EnderDragonEntity.class, 53, 4f, 13f), + ENDER_DRAGON(EnderDragonEntity.class, 53, 0f, 0f), SHULKER(ShulkerEntity.class, 54, 1f, 1f), ENDERMITE(MonsterEntity.class, 55, 0.3f, 0.4f), AGENT(Entity.class, 56, 0f), @@ -166,7 +166,12 @@ public enum EntityType { /** * Not an entity in Bedrock, so we replace it with a Pillager */ - ILLUSIONER(AbstractIllagerEntity.class, 114, 1.8f, 0.6f, 0.6f, 1.62f, "minecraft:pillager"); + ILLUSIONER(AbstractIllagerEntity.class, 114, 1.8f, 0.6f, 0.6f, 1.62f, "minecraft:pillager"), + + /** + * Not an entity in Bedrock, but used for the Ender Dragon's multiple hitboxes + */ + ENDER_DRAGON_PART(EnderDragonPartEntity.class, 32, 0, 0, 0, 0, "minecraft:armor_stand"); private static final EntityType[] VALUES = values(); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java index b81025be..b2a70146 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java @@ -49,6 +49,7 @@ import org.geysermc.connector.entity.CommandBlockMinecartEntity; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.ItemFrameEntity; import org.geysermc.connector.entity.living.merchant.AbstractMerchantEntity; +import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; @@ -251,9 +252,17 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator Date: Thu, 29 Oct 2020 18:40:42 -0400 Subject: [PATCH 019/116] Add support for more recipes (#1434) * Add support for more recipes - Tool repairing - Book cloning - Suspicious stew - Tipped arrows What still needs to be done: - Map cloning/extending (though there may be item mapping mismatch getting in the way) - Banner duplication (couldn't figure this out) * Add some more spacing * Explain recipe UUIDs --- .../translators/item/RecipeRegistry.java | 72 +++++++++++++++++-- .../java/JavaDeclareRecipesTranslator.java | 21 +++++- 2 files changed, 85 insertions(+), 8 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/RecipeRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/RecipeRegistry.java index 191b285c..411c0295 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/RecipeRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/RecipeRegistry.java @@ -34,8 +34,7 @@ import org.geysermc.connector.utils.FileUtils; import org.geysermc.connector.utils.LanguageUtils; import java.io.InputStream; -import java.util.List; -import java.util.UUID; +import java.util.*; /** * Manages any recipe-related storing @@ -46,22 +45,45 @@ public class RecipeRegistry { * A list of all possible leather armor dyeing recipes. * Created manually. */ - public static List LEATHER_DYEING_RECIPES = new ObjectArrayList<>(); + public static final List LEATHER_DYEING_RECIPES = new ObjectArrayList<>(); /** * A list of all possible firework rocket recipes, including the base rocket. * Obtained from a ProxyPass dump of protocol v407 */ - public static List FIREWORK_ROCKET_RECIPES = new ObjectArrayList<>(21); + public static final List FIREWORK_ROCKET_RECIPES = new ObjectArrayList<>(); /** * A list of all possible firework star recipes. * Obtained from a ProxyPass dump of protocol v407 */ - public static List FIREWORK_STAR_RECIPES = new ObjectArrayList<>(40); + public static final List FIREWORK_STAR_RECIPES = new ObjectArrayList<>(); /** * A list of all possible shulker box dyeing options. * Obtained from a ProxyPass dump of protocol v407 */ - public static List SHULKER_BOX_DYEING_RECIPES = new ObjectArrayList<>(); + public static final List SHULKER_BOX_DYEING_RECIPES = new ObjectArrayList<>(); + /** + * A list of all possible suspicious stew recipes. + * Obtained from a ProxyPass dump of protocol v407 + */ + public static final List SUSPICIOUS_STEW_RECIPES = new ObjectArrayList<>(); + /** + * A list of all possible tipped arrow recipes. + * Obtained from a ProxyPass dump of protocol v407 + */ + public static final List TIPPED_ARROW_RECIPES = new ObjectArrayList<>(); + + // TODO: These are the other "multi" UUIDs that supposedly enable various recipes. Find out what each enables. + // 442d85ed-8272-4543-a6f1-418f90ded05d 8b36268c-1829-483c-a0f1-993b7156a8f2 602234e4-cac1-4353-8bb7-b1ebff70024b 98c84b38-1085-46bd-b1ce-dd38c159e6cc + // d81aaeaf-e172-4440-9225-868df030d27b b5c5d105-75a2-4076-af2b-923ea2bf4bf0 00000000-0000-0000-0000-000000000002 85939755-ba10-4d9d-a4cc-efb7a8e943c4 + // d392b075-4ba1-40ae-8789-af868d56f6ce aecd2294-4b94-434b-8667-4499bb2c9327 + /** + * Recipe data that, when sent to the client, enables book cloning + */ + public static final CraftingData BOOK_CLONING_RECIPE_DATA = CraftingData.fromMulti(UUID.fromString("d1ca6b84-338e-4f2f-9c6b-76cc8b4bd98d")); + /** + * Recipe data that, when sent to the client, enables tool repairing in a crafting table + */ + public static final CraftingData TOOL_REPAIRING_RECIPE_DATA = CraftingData.fromMulti(UUID.fromString("00000000-0000-0000-0000-000000000001")); static { // Get all recipes that are not directly sent from a Java server @@ -88,6 +110,12 @@ public class RecipeRegistry { for (JsonNode entry : items.get("shulker_boxes")) { SHULKER_BOX_DYEING_RECIPES.add(getCraftingDataFromJsonNode(entry)); } + for (JsonNode entry : items.get("suspicious_stew")) { + SUSPICIOUS_STEW_RECIPES.add(getCraftingDataFromJsonNode(entry)); + } + for (JsonNode entry : items.get("tipped_arrows")) { + TIPPED_ARROW_RECIPES.add(getCraftingDataFromJsonNode(entry)); + } } /** @@ -97,11 +125,41 @@ public class RecipeRegistry { */ private static CraftingData getCraftingDataFromJsonNode(JsonNode node) { ItemData output = ItemRegistry.getBedrockItemFromJson(node.get("output").get(0)); + UUID uuid = UUID.randomUUID(); + if (node.get("type").asInt() == 1) { + // Shaped recipe + List shape = new ArrayList<>(); + // Get the shape of the recipe + for (JsonNode chars : node.get("shape")) { + shape.add(chars.asText()); + } + + // In recipes.json each recipe is mapped by a letter + Map letterToRecipe = new HashMap<>(); + Iterator> iterator = node.get("input").fields(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + letterToRecipe.put(entry.getKey(), ItemRegistry.getBedrockItemFromJson(entry.getValue())); + } + + ItemData[] inputs = new ItemData[shape.size() * shape.get(0).length()]; + int i = 0; + // Create a linear array of items from the "cube" of the shape + for (int j = 0; i < shape.size() * shape.get(0).length(); j++) { + for (char c : shape.get(j).toCharArray()) { + ItemData data = letterToRecipe.getOrDefault(String.valueOf(c), ItemData.AIR); + inputs[i] = data; + i++; + } + } + + return CraftingData.fromShaped(uuid.toString(), shape.get(0).length(), shape.size(), + inputs, new ItemData[]{output}, uuid, "crafting_table", 0); + } List inputs = new ObjectArrayList<>(); for (JsonNode entry : node.get("input")) { inputs.add(ItemRegistry.getBedrockItemFromJson(entry)); } - UUID uuid = UUID.randomUUID(); if (node.get("type").asInt() == 5) { // Shulker box return CraftingData.fromShulkerBox(uuid.toString(), diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java index 9ffb4f0d..70303baa 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java @@ -80,8 +80,19 @@ public class JavaDeclareRecipesTranslator extends PacketTranslator Date: Sat, 31 Oct 2020 22:38:56 -0400 Subject: [PATCH 020/116] MapItemTranslator: support map tag being of short value (#1475) GSigns sends this as a short. Who knows why. --- .../translators/nbt/MapItemTranslator.java | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/MapItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/MapItemTranslator.java index c9b49efd..d325af48 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/MapItemTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/MapItemTranslator.java @@ -25,10 +25,7 @@ package org.geysermc.connector.network.translators.item.translators.nbt; -import com.github.steveice10.opennbt.tag.builtin.ByteTag; -import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import com.github.steveice10.opennbt.tag.builtin.IntTag; -import com.github.steveice10.opennbt.tag.builtin.LongTag; +import com.github.steveice10.opennbt.tag.builtin.*; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.ItemRemapper; import org.geysermc.connector.network.translators.item.ItemEntry; @@ -39,14 +36,22 @@ public class MapItemTranslator extends NbtItemStackTranslator { @Override public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) { - IntTag mapId = itemTag.get("map"); + // Can be either an IntTag or ShortTag + Tag mapId = itemTag.get("map"); + if (mapId == null) return; - if (mapId != null) { - itemTag.put(new LongTag("map_uuid", mapId.getValue())); - itemTag.put(new IntTag("map_name_index", mapId.getValue())); - itemTag.put(new ByteTag("map_display_players", (byte) 1)); - itemTag.remove("map"); + int mapValue; + if (mapId.getValue() instanceof Short) { + // Convert to int if necessary + mapValue = (int) (short) mapId.getValue(); + } else { + mapValue = (int) mapId.getValue(); } + + itemTag.put(new LongTag("map_uuid", mapValue)); + itemTag.put(new IntTag("map_name_index", mapValue)); + itemTag.put(new ByteTag("map_display_players", (byte) 1)); + itemTag.remove("map"); } @Override From 3e0d898b6a53e655bffd3b72e779054b04b4b765 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Mon, 2 Nov 2020 14:28:31 -0500 Subject: [PATCH 021/116] Update to 1.16.4 (#1487) * 1.16.4-pre1 support * Update to support ViaVersion 3.2.0 (#1358) * Update to support ViaVersion 3.2.0 This commit updates ViaVersion integration to support their recent mappings changes. Co-authored-by: Nassim * Send adventure settings on gamemode change after gamemode swap * Update Velocity to 1.1.0 proper * Update to 1.16.4 Co-authored-by: Nassim --- bootstrap/spigot/pom.xml | 2 +- .../platform/spigot/GeyserSpigotPlugin.java | 8 ++++ .../world/GeyserSpigotWorldManager.java | 38 ++++++++++++++----- bootstrap/velocity/pom.xml | 2 +- .../velocity/GeyserVelocityPlugin.java | 2 +- connector/pom.xml | 4 +- .../world/JavaNotifyClientTranslator.java | 4 +- 7 files changed, 43 insertions(+), 17 deletions(-) diff --git a/bootstrap/spigot/pom.xml b/bootstrap/spigot/pom.xml index d4dc3326..067b446a 100644 --- a/bootstrap/spigot/pom.xml +++ b/bootstrap/spigot/pom.xml @@ -26,7 +26,7 @@ us.myles viaversion - 3.1.1 + 3.2.0 provided diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java index 892f8feb..59a8db06 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java @@ -43,6 +43,7 @@ import org.geysermc.platform.spigot.command.GeyserSpigotCommandManager; import org.geysermc.platform.spigot.command.SpigotCommandSender; import org.geysermc.platform.spigot.world.GeyserSpigotBlockPlaceListener; import org.geysermc.platform.spigot.world.GeyserSpigotWorldManager; +import us.myles.ViaVersion.api.Via; import java.io.File; import java.io.IOException; @@ -121,6 +122,13 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { this.geyserCommandManager = new GeyserSpigotCommandManager(this, connector); boolean isViaVersion = (Bukkit.getPluginManager().getPlugin("ViaVersion") != null); + if (isViaVersion) { + if (!isCompatible(Via.getAPI().getVersion().replace("-SNAPSHOT", ""), "3.2.0")) { + geyserLogger.warning(LanguageUtils.getLocaleStringLog("geyser.bootstrap.viaversion.too_old", + "https://ci.viaversion.com/job/ViaVersion/")); + isViaVersion = false; + } + } // Used to determine if Block.getBlockData() is present. boolean isLegacy = !isCompatible(Bukkit.getServer().getVersion(), "1.13.0"); if (isLegacy) diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java index 8a92526f..ad3d1cf1 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java @@ -26,6 +26,7 @@ package org.geysermc.platform.spigot.world; import com.fasterxml.jackson.databind.JsonNode; +import com.github.steveice10.mc.protocol.MinecraftConstants; import com.github.steveice10.mc.protocol.data.game.chunk.Chunk; import it.unimi.dsi.fastutil.ints.Int2IntMap; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; @@ -41,14 +42,29 @@ import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.utils.FileUtils; import org.geysermc.connector.utils.GameRule; import org.geysermc.connector.utils.LanguageUtils; -import us.myles.ViaVersion.protocols.protocol1_13_1to1_13.Protocol1_13_1To1_13; -import us.myles.ViaVersion.protocols.protocol1_16_2to1_16_1.data.MappingData; +import us.myles.ViaVersion.api.Pair; +import us.myles.ViaVersion.api.protocol.Protocol; +import us.myles.ViaVersion.api.protocol.ProtocolRegistry; +import us.myles.ViaVersion.api.protocol.ProtocolVersion; +import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.Protocol1_13To1_12_2; import java.io.InputStream; +import java.util.List; public class GeyserSpigotWorldManager extends GeyserWorldManager { + /** + * The current client protocol version for ViaVersion usage. + */ + private static final int CLIENT_PROTOCOL_VERSION = MinecraftConstants.PROTOCOL_VERSION; + + /** + * Whether the server is pre-1.13. + */ private final boolean isLegacy; + /** + * Whether the server is pre-1.16 and therefore does not support 3D biomes on an API level guaranteed. + */ private final boolean use3dBiomes; /** * You need ViaVersion to connect to an older server with Geyser. @@ -122,14 +138,16 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { if (isViaVersion) { Block block = world.getBlockAt(x, y, z); // Black magic that gets the old block state ID - int oldBlockId = (block.getType().getId() << 4) | (block.getData() & 0xF); - // Convert block state from old version -> 1.13 -> 1.13.1 -> 1.14 -> 1.15 -> 1.16 -> 1.16.2 - int thirteenBlockId = us.myles.ViaVersion.protocols.protocol1_13to1_12_2.data.MappingData.blockMappings.getNewId(oldBlockId); - int thirteenPointOneBlockId = Protocol1_13_1To1_13.getNewBlockStateId(thirteenBlockId); - int fourteenBlockId = us.myles.ViaVersion.protocols.protocol1_14to1_13_2.data.MappingData.blockStateMappings.getNewId(thirteenPointOneBlockId); - int fifteenBlockId = us.myles.ViaVersion.protocols.protocol1_15to1_14_4.data.MappingData.blockStateMappings.getNewId(fourteenBlockId); - int sixteenBlockId = us.myles.ViaVersion.protocols.protocol1_16to1_15_2.data.MappingData.blockStateMappings.getNewId(fifteenBlockId); - return MappingData.blockStateMappings.getNewId(sixteenBlockId); + int blockId = (block.getType().getId() << 4) | (block.getData() & 0xF); + // Convert block state from old version (1.12.2) -> 1.13 -> 1.13.1 -> 1.14 -> 1.15 -> 1.16 -> 1.16.2 + blockId = ProtocolRegistry.getProtocol(Protocol1_13To1_12_2.class).getMappingData().getNewBlockId(blockId); + List> protocolList = ProtocolRegistry.getProtocolPath(CLIENT_PROTOCOL_VERSION, + ProtocolVersion.v1_13.getId()); + for (int i = protocolList.size() - 1; i >= 0; i--) { + if (protocolList.get(i).getValue().getMappingData() == null) continue; + blockId = protocolList.get(i).getValue().getMappingData().getNewBlockStateId(blockId); + } + return blockId; } else { return BlockTranslator.AIR; } diff --git a/bootstrap/velocity/pom.xml b/bootstrap/velocity/pom.xml index ee445b6e..babb9a3e 100644 --- a/bootstrap/velocity/pom.xml +++ b/bootstrap/velocity/pom.xml @@ -20,7 +20,7 @@ com.velocitypowered velocity-api - 1.0.0-SNAPSHOT + 1.1.0 provided diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java index 291d7a00..f75c683a 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java @@ -121,7 +121,7 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { this.connector = GeyserConnector.start(PlatformType.VELOCITY, this); this.geyserCommandManager = new GeyserVelocityCommandManager(connector); - this.commandManager.register(new GeyserVelocityCommandExecutor(connector), "geyser"); + this.commandManager.register("geyser", new GeyserVelocityCommandExecutor(connector)); if (geyserConfig.isLegacyPingPassthrough()) { this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(connector); } else { diff --git a/connector/pom.xml b/connector/pom.xml index 19731987..edf14abb 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -109,9 +109,9 @@ compile - com.github.steveice10 + com.github.GeyserMC mcprotocollib - 1b01b1ffef + e4181064d1 compile diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java index 1e102a60..26ecb1e1 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java @@ -103,13 +103,13 @@ public class JavaNotifyClientTranslator extends PacketTranslator Date: Mon, 2 Nov 2020 16:04:08 -0500 Subject: [PATCH 022/116] Send position update every 3 seconds if idle (#1421) * Send position update every 3 seconds if idle Prevents timeouts in certain instances when AFK. * Cancel position sending on dimension switching * Remove debug lines * Create function to centralize movement translation --- .../network/session/GeyserSession.java | 6 ++ .../player/BedrockMovePlayerTranslator.java | 56 +++++++++++++++---- .../connector/utils/DimensionUtils.java | 4 ++ 3 files changed, 55 insertions(+), 11 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index ac72d219..6a2913c8 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -221,6 +221,12 @@ public class GeyserSession implements CommandSender { @Setter private ScheduledFuture bucketScheduledFuture; + /** + * Sends a movement packet every three seconds if the player hasn't moved. Prevents timeouts when AFK in certain instances. + */ + @Setter + private ScheduledFuture movementSendIfIdle; + private boolean reducedDebugInfo = false; @Setter diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockMovePlayerTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockMovePlayerTranslator.java index 8809941b..c5988bf0 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockMovePlayerTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockMovePlayerTranslator.java @@ -25,7 +25,13 @@ package org.geysermc.connector.network.translators.bedrock.entity.player; +import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPositionPacket; +import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPositionRotationPacket; import com.nukkitx.math.vector.Vector3d; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket; +import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket; +import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket; import org.geysermc.connector.common.ChatColor; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.PlayerEntity; @@ -34,11 +40,7 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; -import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPositionRotationPacket; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket; -import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket; -import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket; +import java.util.concurrent.TimeUnit; @Translator(packet = MovePlayerPacket.class) public class BedrockMovePlayerTranslator extends PacketTranslator { @@ -59,13 +61,11 @@ public class BedrockMovePlayerTranslator extends PacketTranslator sendPositionIfIdle(session), + 3, TimeUnit.SECONDS)); + } + + /** + * Adjust the Bedrock position before sending to the Java server to account for inaccuracies in movement between + * the two versions. + * + * @param position the current Bedrock position of the client + * @param onGround whether the Bedrock player is on the ground + * @return the position to send to the Java server. + */ + private Vector3d adjustBedrockPosition(Vector3f position, boolean onGround) { + // We need to parse the float as a string since casting a float to a double causes us to + // lose precision and thus, causes players to get stuck when walking near walls + double javaY = position.getY() - EntityType.PLAYER.getOffset(); + if (onGround) javaY = Math.ceil(javaY * 2) / 2; + + return Vector3d.from(Double.parseDouble(Float.toString(position.getX())), javaY, + Double.parseDouble(Float.toString(position.getZ()))); } public boolean isValidMove(GeyserSession session, MovePlayerPacket.Mode mode, Vector3f currentPosition, Vector3f newPosition) { @@ -147,4 +169,16 @@ public class BedrockMovePlayerTranslator extends PacketTranslator sendPositionIfIdle(session), + 3, TimeUnit.SECONDS)); + } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/DimensionUtils.java b/connector/src/main/java/org/geysermc/connector/utils/DimensionUtils.java index 7b283e9c..30d69258 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/DimensionUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/DimensionUtils.java @@ -52,6 +52,10 @@ public class DimensionUtils { if (javaDimension.equals(player.getDimension())) return; + if (session.getMovementSendIfIdle() != null) { + session.getMovementSendIfIdle().cancel(true); + } + session.getEntityCache().removeAllEntities(); session.getItemFrameCache().clear(); if (session.getPendingDimSwitches().getAndIncrement() > 0) { From ea521078e11b9d158fa28427d940fb191dc3d671 Mon Sep 17 00:00:00 2001 From: Matthias Neid Date: Mon, 2 Nov 2020 22:44:45 +0100 Subject: [PATCH 023/116] Update supported version in README to 1.16.4 (#1488) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f7201bb0..e2efc5b4 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,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 now joined us here! -### Currently supporting Minecraft Bedrock v1.16.x and Minecraft Java v1.16.3. +### Currently supporting Minecraft Bedrock v1.16.x and Minecraft Java v1.16.4. ## Setting Up Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set up Geyser. From ce64dd27887789cff36b0e9c8bf2be160993f57d Mon Sep 17 00:00:00 2001 From: David Choo Date: Wed, 4 Nov 2020 20:30:55 -0500 Subject: [PATCH 024/116] Fix sitting with armor stand and animal mount offsets (#1492) * Fix sitting with armor stand and animal offsets * Fix riding players * Moved @Getter --- .../entity/living/ArmorStandEntity.java | 4 ++- .../JavaEntitySetPassengersTranslator.java | 34 ++++++++++++------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/ArmorStandEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/ArmorStandEntity.java index 07496093..b61aeda9 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/ArmorStandEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/ArmorStandEntity.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.MetadataType; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import lombok.Getter; import org.geysermc.connector.entity.LivingEntity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; @@ -36,6 +37,7 @@ import org.geysermc.connector.network.session.GeyserSession; public class ArmorStandEntity extends LivingEntity { // These are used to store the state of the armour stand for use when handling invisibility + @Getter private boolean isMarker = false; private boolean isInvisible = false; private boolean isSmall = false; @@ -47,7 +49,7 @@ public class ArmorStandEntity extends LivingEntity { @Override public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) { // Fake the height to be above where it is so the nametag appears in the right location for invisible non-marker armour stands - if (!isMarker && isInvisible) { + if (!isMarker && isInvisible && passengers.isEmpty()) { position = position.add(0d, entityType.getHeight() * (isSmall ? 0.55d : 1d), 0d); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntitySetPassengersTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntitySetPassengersTranslator.java index 64f0e3e9..0fecf118 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntitySetPassengersTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntitySetPassengersTranslator.java @@ -33,6 +33,8 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityLinkData; import com.nukkitx.protocol.bedrock.packet.SetEntityLinkPacket; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import org.geysermc.connector.entity.Entity; +import org.geysermc.connector.entity.living.ArmorStandEntity; +import org.geysermc.connector.entity.living.animal.AnimalEntity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; @@ -46,6 +48,10 @@ public class JavaEntitySetPassengersTranslator extends PacketTranslator Date: Thu, 5 Nov 2020 14:59:01 -0500 Subject: [PATCH 025/116] Fix bell sound and visuals (#1502) * Fix bell sound and visuals The bell sound now correctly plays. Bells will also visually ring when rung by another player. * Compress elses * Add more whitespace to new code --- .../java/world/JavaBlockValueTranslator.java | 51 +++++++++++++++---- connector/src/main/resources/languages | 2 +- connector/src/main/resources/mappings | 2 +- 3 files changed, 42 insertions(+), 13 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockValueTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockValueTranslator.java index 3d3df51c..903cad0c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockValueTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockValueTranslator.java @@ -25,6 +25,7 @@ package org.geysermc.connector.network.translators.java.world; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.github.steveice10.mc.protocol.data.game.world.block.value.*; import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerBlockValuePacket; import com.nukkitx.math.vector.Vector3i; @@ -53,16 +54,12 @@ public class JavaBlockValueTranslator extends PacketTranslator 0 ? 1 : 0); session.sendUpstreamPacket(blockEventPacket); - } - if (packet.getValue() instanceof EndGatewayValue) { + } else if (packet.getValue() instanceof EndGatewayValue) { blockEventPacket.setEventType(1); session.sendUpstreamPacket(blockEventPacket); - } - if (packet.getValue() instanceof NoteBlockValue) { + } else if (packet.getValue() instanceof NoteBlockValue) { NoteblockBlockEntityTranslator.translate(session, packet.getPosition()); - return; - } - if (packet.getValue() instanceof PistonValue) { + } else if (packet.getValue() instanceof PistonValue) { PistonValueType type = (PistonValueType) packet.getType(); // Unlike everything else, pistons need a block entity packet to convey motion @@ -73,14 +70,46 @@ public class JavaBlockValueTranslator extends PacketTranslator Date: Thu, 5 Nov 2020 22:36:22 +0100 Subject: [PATCH 026/116] Block entity performance improvements (#1481) * BlockEntity performance improvements * Use chunk cache if possible for block caching * Get new block state from ViaVersion if block entity * Add Javadoc for FlowerPotBlockEntityTranslator.isFlowerBlock * Remove debug line * Don't add all RequiresBlockState instances if cache chunks is enabled * Double chest map get optimization * Last changes Co-authored-by: DoctorMacc --- .../world/GeyserSpigotWorldManager.java | 58 ++++++---- .../connector/entity/ItemFrameEntity.java | 13 +-- ...BedrockInventoryTransactionTranslator.java | 7 +- .../translators/item/ItemRegistry.java | 10 +- .../translators/item/ItemTranslator.java | 7 +- .../item/translators/BannerTranslator.java | 108 +++++++++--------- .../java/world/JavaChunkDataTranslator.java | 7 +- .../world/JavaUpdateTileEntityTranslator.java | 25 ++-- .../world/block/BlockStateValues.java | 41 +++---- .../entity/BannerBlockEntityTranslator.java | 18 +-- .../entity/BedBlockEntityTranslator.java | 16 +-- .../block/entity/BedrockOnlyBlockEntity.java | 3 +- .../block/entity/BlockEntityTranslator.java | 30 +++-- .../entity/CampfireBlockEntityTranslator.java | 10 +- .../CommandBlockBlockEntityTranslator.java | 33 +++--- .../DoubleChestBlockEntityTranslator.java | 59 ++++------ .../entity/EmptyBlockEntityTranslator.java | 9 +- .../EndGatewayBlockEntityTranslator.java | 15 +-- .../FlowerPotBlockEntityTranslator.java | 46 +++++--- .../JigsawBlockBlockEntityTranslator.java | 19 ++- .../NoteblockBlockEntityTranslator.java | 7 +- .../entity/PistonBlockEntityTranslator.java | 20 ++-- .../ShulkerBoxBlockEntityTranslator.java | 17 +-- .../entity/SignBlockEntityTranslator.java | 73 ++++++------ .../entity/SkullBlockEntityTranslator.java | 19 ++- .../entity/SpawnerBlockEntityTranslator.java | 57 +++++---- .../connector/utils/BlockEntityUtils.java | 15 ++- .../geysermc/connector/utils/ChunkUtils.java | 24 ++-- 28 files changed, 362 insertions(+), 404 deletions(-) diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java index ad3d1cf1..28b2da3a 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java @@ -43,16 +43,19 @@ import org.geysermc.connector.utils.FileUtils; import org.geysermc.connector.utils.GameRule; import org.geysermc.connector.utils.LanguageUtils; import us.myles.ViaVersion.api.Pair; +import us.myles.ViaVersion.api.Via; +import us.myles.ViaVersion.api.data.MappingData; +import us.myles.ViaVersion.api.minecraft.Position; import us.myles.ViaVersion.api.protocol.Protocol; import us.myles.ViaVersion.api.protocol.ProtocolRegistry; import us.myles.ViaVersion.api.protocol.ProtocolVersion; import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.Protocol1_13To1_12_2; +import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.storage.BlockStorage; import java.io.InputStream; import java.util.List; public class GeyserSpigotWorldManager extends GeyserWorldManager { - /** * The current client protocol version for ViaVersion usage. */ @@ -99,8 +102,9 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { } // Only load in the biomes that are present in this version of Minecraft for (Biome enumBiome : Biome.values()) { - if (biomes.has(enumBiome.toString())) { - biomeToIdMap.put(enumBiome.ordinal(), biomes.get(enumBiome.toString()).intValue()); + JsonNode biome = biomes.get(enumBiome.toString()); + if (biome != null) { + biomeToIdMap.put(enumBiome.ordinal(), biome.intValue()); } else { GeyserConnector.getInstance().getLogger().debug("No biome mapping found for " + enumBiome.toString() + ", defaulting to 0"); @@ -127,30 +131,38 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { public static int getLegacyBlock(GeyserSession session, int x, int y, int z, boolean isViaVersion) { if (isViaVersion) { - return getLegacyBlock(Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld(), x, y, z, true); + Player bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername()); + // Get block entity storage + BlockStorage storage = Via.getManager().getConnection(bukkitPlayer.getUniqueId()).get(BlockStorage.class); + return getLegacyBlock(storage, bukkitPlayer.getWorld(), x, y, z); } else { return BlockTranslator.AIR; } } @SuppressWarnings("deprecation") - public static int getLegacyBlock(World world, int x, int y, int z, boolean isViaVersion) { - if (isViaVersion) { - Block block = world.getBlockAt(x, y, z); - // Black magic that gets the old block state ID - int blockId = (block.getType().getId() << 4) | (block.getData() & 0xF); - // Convert block state from old version (1.12.2) -> 1.13 -> 1.13.1 -> 1.14 -> 1.15 -> 1.16 -> 1.16.2 - blockId = ProtocolRegistry.getProtocol(Protocol1_13To1_12_2.class).getMappingData().getNewBlockId(blockId); - List> protocolList = ProtocolRegistry.getProtocolPath(CLIENT_PROTOCOL_VERSION, - ProtocolVersion.v1_13.getId()); - for (int i = protocolList.size() - 1; i >= 0; i--) { - if (protocolList.get(i).getValue().getMappingData() == null) continue; - blockId = protocolList.get(i).getValue().getMappingData().getNewBlockStateId(blockId); + public static int getLegacyBlock(BlockStorage storage, World world, int x, int y, int z) { + Block block = world.getBlockAt(x, y, z); + // Black magic that gets the old block state ID + int blockId = (block.getType().getId() << 4) | (block.getData() & 0xF); + // Convert block state from old version (1.12.2) -> 1.13 -> 1.13.1 -> 1.14 -> 1.15 -> 1.16 -> 1.16.2 + blockId = ProtocolRegistry.getProtocol(Protocol1_13To1_12_2.class).getMappingData().getNewBlockId(blockId); + List> protocolList = ProtocolRegistry.getProtocolPath(CLIENT_PROTOCOL_VERSION, + ProtocolVersion.v1_13.getId()); + // Translate block entity differences - some information was stored in block tags and not block states + if (storage.isWelcome(blockId)) { // No getOrDefault method + BlockStorage.ReplacementData data = storage.get(new Position(x, (short) y, z)); + if (data != null && data.getReplacement() != -1) { + blockId = data.getReplacement(); } - return blockId; - } else { - return BlockTranslator.AIR; } + for (int i = protocolList.size() - 1; i >= 0; i--) { + MappingData mappingData = protocolList.get(i).getValue().getMappingData(); + if (mappingData != null) { + blockId = mappingData.getNewBlockStateId(blockId); + } + } + return blockId; } @Override @@ -162,11 +174,13 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { return; } World world = bukkitPlayer.getWorld(); - if (this.isLegacy) { + if (this.isLegacy) { + // Get block entity storage + BlockStorage storage = Via.getManager().getConnection(bukkitPlayer.getUniqueId()).get(BlockStorage.class); for (int blockY = 0; blockY < 16; blockY++) { // Cache-friendly iteration order for (int blockZ = 0; blockZ < 16; blockZ++) { for (int blockX = 0; blockX < 16; blockX++) { - chunk.set(blockX, blockY, blockZ, getLegacyBlock(world, (x << 4) + blockX, (y << 4) + blockY, (z << 4) + blockZ, true)); + chunk.set(blockX, blockY, blockZ, getLegacyBlock(storage, world, (x << 4) + blockX, (y << 4) + blockY, (z << 4) + blockZ)); } } } @@ -176,7 +190,7 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { for (int blockZ = 0; blockZ < 16; blockZ++) { for (int blockX = 0; blockX < 16; blockX++) { Block block = world.getBlockAt((x << 4) + blockX, (y << 4) + blockY, (z << 4) + blockZ); - int id = BlockTranslator.getJavaIdBlockMap().getOrDefault(block.getBlockData().getAsString(), 0); + int id = BlockTranslator.getJavaIdBlockMap().getOrDefault(block.getBlockData().getAsString(), BlockTranslator.AIR); chunk.set(blockX, blockY, blockZ, id); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java b/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java index f9d2ace4..501c7e46 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java @@ -141,6 +141,7 @@ public class ItemFrameEntity extends Entity { UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket(); updateBlockPacket.setDataLayer(0); updateBlockPacket.setBlockPosition(bedrockPosition); + // TODO 1.16.100 set to BEDROCK_AIR updateBlockPacket.setRuntimeId(0); updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.PRIORITY); updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NETWORK); @@ -196,18 +197,6 @@ public class ItemFrameEntity extends Entity { return session.getItemFrameCache().getOrDefault(position, -1); } - /** - * Determines if the position contains an item frame. - * Does largely the same thing as getItemFrameEntityId, but for speed purposes is implemented separately, - * since every block destroy packet has to check for an item frame. - * @param position position of block. - * @param session GeyserSession. - * @return true if position contains item frame, false if not. - */ - public static boolean positionContainsItemFrame(GeyserSession session, Vector3i position) { - return session.getItemFrameCache().containsKey(position); - } - /** * Force-remove from the position-to-ID map so it doesn't cause conflicts. * @param session GeyserSession. diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java index b2a70146..81a2cd50 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java @@ -194,10 +194,9 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator ITEM_ENTRIES.values() - .stream().filter(itemEntry -> itemEntry.getJavaIdentifier().equals(key)).findFirst().orElse(null)); + return JAVA_IDENTIFIER_MAP.computeIfAbsent(javaIdentifier, key -> { + for (ItemEntry entry : ITEM_ENTRIES.values()) { + if (entry.getJavaIdentifier().equals(key)) { + return entry; + } + } + return null; + }); } /** diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java index f95a0ccc..55db9a25 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java @@ -51,7 +51,6 @@ import java.util.*; import java.util.stream.Collectors; public abstract class ItemTranslator { - private static final Int2ObjectMap ITEM_STACK_TRANSLATORS = new Int2ObjectOpenHashMap<>(); private static final List NBT_TRANSLATORS; @@ -220,7 +219,7 @@ public abstract class ItemTranslator { public abstract List getAppliedItems(); public NbtMap translateNbtToBedrock(com.github.steveice10.opennbt.tag.builtin.CompoundTag tag) { - Map javaValue = new HashMap<>(); + NbtMapBuilder builder = NbtMap.builder(); if (tag.getValue() != null && !tag.getValue().isEmpty()) { for (String str : tag.getValue().keySet()) { com.github.steveice10.opennbt.tag.builtin.Tag javaTag = tag.get(str); @@ -228,11 +227,9 @@ public abstract class ItemTranslator { if (translatedTag == null) continue; - javaValue.put(javaTag.getName(), translatedTag); + builder.put(javaTag.getName(), translatedTag); } } - NbtMapBuilder builder = NbtMap.builder(); - javaValue.forEach(builder::put); return builder.build(); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java index f4bfdfb6..200271cf 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java @@ -26,20 +26,16 @@ package org.geysermc.connector.network.translators.item.translators; 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.IntTag; -import com.github.steveice10.opennbt.tag.builtin.ListTag; -import com.github.steveice10.opennbt.tag.builtin.StringTag; -import com.github.steveice10.opennbt.tag.builtin.Tag; +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.connector.network.translators.ItemRemapper; +import org.geysermc.connector.network.translators.item.ItemEntry; import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.network.translators.item.ItemTranslator; -import org.geysermc.connector.network.translators.item.ItemEntry; import java.util.ArrayList; import java.util.HashMap; @@ -49,54 +45,13 @@ import java.util.stream.Collectors; @ItemRemapper public class BannerTranslator extends ItemTranslator { - private final List appliedItems; public BannerTranslator() { - appliedItems = ItemRegistry.ITEM_ENTRIES.values().stream().filter(entry -> entry.getJavaIdentifier().endsWith("banner")).collect(Collectors.toList()); - } - - @Override - public ItemData translateToBedrock(ItemStack itemStack, ItemEntry itemEntry) { - if (itemStack.getNbt() == null) return super.translateToBedrock(itemStack, itemEntry); - - ItemData itemData = super.translateToBedrock(itemStack, itemEntry); - - CompoundTag blockEntityTag = itemStack.getNbt().get("BlockEntityTag"); - if (blockEntityTag.contains("Patterns")) { - ListTag patterns = blockEntityTag.get("Patterns"); - - NbtMapBuilder builder = itemData.getTag().toBuilder(); - builder.put("Patterns", convertBannerPattern(patterns)); - - itemData = ItemData.of(itemData.getId(), itemData.getDamage(), itemData.getCount(), builder.build()); - } - - return itemData; - } - - @Override - public ItemStack translateToJava(ItemData itemData, ItemEntry itemEntry) { - if (itemData.getTag() == null) return super.translateToJava(itemData, itemEntry); - - ItemStack itemStack = super.translateToJava(itemData, itemEntry); - - NbtMap nbtTag = itemData.getTag(); - if (nbtTag.containsKey("Patterns", NbtType.COMPOUND)) { - List patterns = nbtTag.getList("Patterns", NbtType.COMPOUND); - - CompoundTag blockEntityTag = new CompoundTag("BlockEntityTag"); - blockEntityTag.put(convertBannerPattern(patterns)); - - itemStack.getNbt().put(blockEntityTag); - } - - return itemStack; - } - - @Override - public List getAppliedItems() { - return appliedItems; + appliedItems = ItemRegistry.ITEM_ENTRIES.values() + .stream() + .filter(entry -> entry.getJavaIdentifier().endsWith("banner")) + .collect(Collectors.toList()); } /** @@ -133,7 +88,6 @@ public class BannerTranslator extends ItemTranslator { return NbtMap.builder() .putInt("Color", 15 - (int) pattern.get("Color").getValue()) - .putString("Pattern", (String) pattern.get("Pattern").getValue()) .putString("Pattern", patternName) .build(); } @@ -147,8 +101,7 @@ public class BannerTranslator extends ItemTranslator { public static ListTag convertBannerPattern(List patterns) { List tagsList = new ArrayList<>(); for (Object patternTag : patterns) { - CompoundTag newPatternTag = getJavaBannerPattern((NbtMap) patternTag); - tagsList.add(newPatternTag); + tagsList.add(getJavaBannerPattern((NbtMap) patternTag)); } return new ListTag("Patterns", tagsList); @@ -167,4 +120,51 @@ public class BannerTranslator extends ItemTranslator { return new CompoundTag("", tags); } + + @Override + public ItemData translateToBedrock(ItemStack itemStack, ItemEntry itemEntry) { + if (itemStack.getNbt() == null) { + return super.translateToBedrock(itemStack, itemEntry); + } + + ItemData itemData = super.translateToBedrock(itemStack, itemEntry); + + CompoundTag blockEntityTag = itemStack.getNbt().get("BlockEntityTag"); + if (blockEntityTag.contains("Patterns")) { + ListTag patterns = blockEntityTag.get("Patterns"); + + NbtMapBuilder builder = itemData.getTag().toBuilder(); + builder.put("Patterns", convertBannerPattern(patterns)); + + itemData = ItemData.of(itemData.getId(), itemData.getDamage(), itemData.getCount(), builder.build()); + } + + return itemData; + } + + @Override + public ItemStack translateToJava(ItemData itemData, ItemEntry itemEntry) { + if (itemData.getTag() == null) { + return super.translateToJava(itemData, itemEntry); + } + + ItemStack itemStack = super.translateToJava(itemData, itemEntry); + + NbtMap nbtTag = itemData.getTag(); + if (nbtTag.containsKey("Patterns", NbtType.COMPOUND)) { + List patterns = nbtTag.getList("Patterns", NbtType.COMPOUND); + + CompoundTag blockEntityTag = new CompoundTag("BlockEntityTag"); + blockEntityTag.put(convertBannerPattern(patterns)); + + itemStack.getNbt().put(blockEntityTag); + } + + return itemStack; + } + + @Override + public List getAppliedItems() { + return appliedItems; + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaChunkDataTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaChunkDataTranslator.java index cd1a321c..741632bc 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaChunkDataTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaChunkDataTranslator.java @@ -45,14 +45,13 @@ import org.geysermc.connector.utils.ChunkUtils; @Translator(packet = ServerChunkDataPacket.class) public class JavaChunkDataTranslator extends PacketTranslator { - /** * Determines if we should process non-full chunks */ - private final boolean isCacheChunks; + private final boolean cacheChunks; public JavaChunkDataTranslator() { - isCacheChunks = GeyserConnector.getInstance().getConfig().isCacheChunks(); + cacheChunks = GeyserConnector.getInstance().getConfig().isCacheChunks(); } @Override @@ -61,7 +60,7 @@ public class JavaChunkDataTranslator extends PacketTranslator { + private final boolean cacheChunks; + + public JavaUpdateTileEntityTranslator() { + cacheChunks = GeyserConnector.getInstance().getConfig().isCacheChunks(); + } @Override public void translate(ServerUpdateTileEntityPacket packet, GeyserSession session) { @@ -48,16 +54,17 @@ public class JavaUpdateTileEntityTranslator extends PacketTranslator= 2 && session.getGameMode() == GameMode.CREATIVE && packet.getNbt().size() > 5) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java index 305118e6..bfd59cc7 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java @@ -36,7 +36,6 @@ import java.util.Map; * Used for block entities if the Java block state contains Bedrock block information. */ public class BlockStateValues { - private static final Int2IntMap BANNER_COLORS = new Int2IntOpenHashMap(); private static final Int2ByteMap BED_COLORS = new Int2ByteOpenHashMap(); private static final Int2ByteMap COMMAND_BLOCK_VALUES = new Int2ByteOpenHashMap(); @@ -52,7 +51,8 @@ public class BlockStateValues { /** * Determines if the block state contains Bedrock block information - * @param entry The String to JsonNode map used in BlockTranslator + * + * @param entry The String to JsonNode map used in BlockTranslator * @param javaBlockState the Java Block State of the block */ public static void storeBlockStateValues(Map.Entry entry, int javaBlockState) { @@ -101,7 +101,7 @@ public class BlockStateValues { } JsonNode skullVariation = entry.getValue().get("variation"); - if(skullVariation != null) { + if (skullVariation != null) { SKULL_VARIANTS.put(javaBlockState, (byte) skullVariation.intValue()); } @@ -124,10 +124,7 @@ public class BlockStateValues { * @return Banner color integer or -1 if no color */ public static int getBannerColor(int state) { - if (BANNER_COLORS.containsKey(state)) { - return BANNER_COLORS.get(state); - } - return -1; + return BANNER_COLORS.getOrDefault(state, -1); } /** @@ -138,10 +135,7 @@ public class BlockStateValues { * @return Bed color byte or -1 if no color */ public static byte getBedColor(int state) { - if (BED_COLORS.containsKey(state)) { - return BED_COLORS.get(state); - } - return -1; + return BED_COLORS.getOrDefault(state, (byte) -1); } /** @@ -157,6 +151,7 @@ public class BlockStateValues { /** * All double chest values are part of the block state in Java and part of the block entity tag in Bedrock. * This gives the DoubleChestValue that can be calculated into the final tag. + * * @return The map of all DoubleChestValues. */ public static Int2ObjectMap getDoubleChestValues() { @@ -165,6 +160,7 @@ public class BlockStateValues { /** * Get the Int2ObjectMap of flower pot block states to containing plant + * * @return Int2ObjectMap of flower pot values */ public static Int2ObjectMap getFlowerPotValues() { @@ -173,6 +169,7 @@ public class BlockStateValues { /** * Get the map of contained flower pot plants to Bedrock CompoundTag + * * @return Map of flower pot blocks. */ public static Map getFlowerPotBlocks() { @@ -182,18 +179,17 @@ public class BlockStateValues { /** * The note that noteblocks output when hit is part of the block state in Java but sent as a BlockEventPacket in Bedrock. * This gives an integer pitch that Bedrock can use. + * * @param state BlockState of the block * @return note block note integer or -1 if not present */ public static int getNoteblockPitch(int state) { - if (NOTEBLOCK_PITCHES.containsKey(state)) { - return NOTEBLOCK_PITCHES.get(state); - } - return -1; + return NOTEBLOCK_PITCHES.getOrDefault(state, -1); } /** * Get the Int2BooleanMap showing if a piston block state is extended or not. + * * @return the Int2BooleanMap of piston extensions. */ public static Int2BooleanMap getPistonValues() { @@ -212,10 +208,7 @@ public class BlockStateValues { * @return Skull variant byte or -1 if no variant */ public static byte getSkullVariant(int state) { - if (SKULL_VARIANTS.containsKey(state)) { - return SKULL_VARIANTS.get(state); - } - return -1; + return SKULL_VARIANTS.getOrDefault(state, (byte) -1); } /** @@ -226,10 +219,7 @@ public class BlockStateValues { * @return Skull rotation value or -1 if no value */ public static byte getSkullRotation(int state) { - if (SKULL_ROTATIONS.containsKey(state)) { - return SKULL_ROTATIONS.get(state); - } - return -1; + return SKULL_ROTATIONS.getOrDefault(state, (byte) -1); } @@ -241,9 +231,6 @@ public class BlockStateValues { * @return Shulker direction value or -1 if no value */ public static byte getShulkerBoxDirection(int state) { - if (SHULKERBOX_DIRECTIONS.containsKey(state)) { - return SHULKERBOX_DIRECTIONS.get(state); - } - return -1; + return SHULKERBOX_DIRECTIONS.getOrDefault(state, (byte) -1); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java index 57393a6c..f5e1d594 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java @@ -27,39 +27,31 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.ListTag; +import com.nukkitx.nbt.NbtMapBuilder; import org.geysermc.connector.network.translators.item.translators.BannerTranslator; import org.geysermc.connector.network.translators.world.block.BlockStateValues; -import java.util.HashMap; -import java.util.Map; - @BlockEntity(name = "Banner", regex = "banner") public class BannerBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { - @Override public boolean isBlock(int blockState) { return BlockStateValues.getBannerColor(blockState) != -1; } @Override - public Map translateTag(CompoundTag tag, int blockState) { - Map tags = new HashMap<>(); - + public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { int bannerColor = BlockStateValues.getBannerColor(blockState); if (bannerColor != -1) { - tags.put("Base", 15 - bannerColor); + builder.put("Base", 15 - bannerColor); } if (tag.contains("Patterns")) { ListTag patterns = tag.get("Patterns"); - tags.put("Patterns", BannerTranslator.convertBannerPattern(patterns)); + builder.put("Patterns", BannerTranslator.convertBannerPattern(patterns)); } if (tag.contains("CustomName")) { - tags.put("CustomName", tag.get("CustomName").getValue()); + builder.put("CustomName", tag.get("CustomName").getValue()); } - - return tags; } - } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedBlockEntityTranslator.java index 080bdc3b..0067cc41 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedBlockEntityTranslator.java @@ -26,27 +26,23 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.nukkitx.nbt.NbtMapBuilder; import org.geysermc.connector.network.translators.world.block.BlockStateValues; -import java.util.HashMap; -import java.util.Map; - @BlockEntity(name = "Bed", regex = "bed") public class BedBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { - @Override public boolean isBlock(int blockState) { return BlockStateValues.getBedColor(blockState) != -1; } @Override - public Map translateTag(CompoundTag tag, int blockState) { - Map tags = new HashMap<>(); + public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { byte bedcolor = BlockStateValues.getBedColor(blockState); // Just in case... - if (bedcolor == -1) bedcolor = 0; - tags.put("color", bedcolor); - return tags; + if (bedcolor == -1) { + bedcolor = 0; + } + builder.put("color", bedcolor); } - } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedrockOnlyBlockEntity.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedrockOnlyBlockEntity.java index d2e4537f..646929f3 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedrockOnlyBlockEntity.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedrockOnlyBlockEntity.java @@ -33,7 +33,6 @@ import org.geysermc.connector.network.session.GeyserSession; * Implemented only if a block is a block entity in Bedrock and not Java Edition. */ public interface BedrockOnlyBlockEntity { - /** * Update the block on Bedrock Edition. * @param session GeyserSession. @@ -49,7 +48,7 @@ public interface BedrockOnlyBlockEntity { * @return Bedrock tag, or null if not a Bedrock-only Block Entity */ static NbtMap getTag(Vector3i position, int blockState) { - if (new FlowerPotBlockEntityTranslator().isBlock(blockState)) { + if (FlowerPotBlockEntityTranslator.isFlowerBlock(blockState)) { return FlowerPotBlockEntityTranslator.getTag(blockState, position); } else if (PistonBlockEntityTranslator.isBlock(blockState)) { return PistonBlockEntityTranslator.getTag(blockState, position); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java index 4df4fd95..67963652 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java @@ -41,10 +41,16 @@ import org.reflections.Reflections; import java.util.HashMap; import java.util.Map; +/** + * The class that all block entities (on both Java and Bedrock) should translate with + */ public abstract class BlockEntityTranslator { - public static final Map BLOCK_ENTITY_TRANSLATORS = new HashMap<>(); - public static ObjectArrayList REQUIRES_BLOCK_STATE_LIST = new ObjectArrayList<>(); + /** + * A list of all block entities that require the Java block state in order to fill out their block entity information. + * This list will be smaller with cache chunks on as we don't need to double-cache data + */ + public static final ObjectArrayList REQUIRES_BLOCK_STATE_LIST = new ObjectArrayList<>(); /** * Contains a list of irregular block entity name translations that can't be fit into the regex @@ -78,27 +84,33 @@ public abstract class BlockEntityTranslator { GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.network.translator.block_entity.failed", clazz.getCanonicalName())); } } + boolean cacheChunks = GeyserConnector.getInstance().getConfig().isCacheChunks(); for (Class clazz : ref.getSubTypesOf(RequiresBlockState.class)) { GeyserConnector.getInstance().getLogger().debug("Found block entity that requires block state: " + clazz.getCanonicalName()); try { - REQUIRES_BLOCK_STATE_LIST.add((RequiresBlockState) clazz.newInstance()); + RequiresBlockState requiresBlockState = (RequiresBlockState) clazz.newInstance(); + if (cacheChunks && !(requiresBlockState instanceof BedrockOnlyBlockEntity)) { + // Not needed to put this one in the map; cache chunks takes care of that for us + GeyserConnector.getInstance().getLogger().debug("Not adding because cache chunks is enabled."); + continue; + } + REQUIRES_BLOCK_STATE_LIST.add(requiresBlockState); } catch (InstantiationException | IllegalAccessException e) { GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.network.translator.block_state.failed", clazz.getCanonicalName())); } } } - public abstract Map translateTag(CompoundTag tag, int blockState); + public abstract void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState); public NbtMap getBlockEntityTag(String id, CompoundTag tag, int blockState) { - int x = Integer.parseInt(String.valueOf(tag.getValue().get("x").getValue())); - int y = Integer.parseInt(String.valueOf(tag.getValue().get("y").getValue())); - int z = Integer.parseInt(String.valueOf(tag.getValue().get("z").getValue())); + int x = ((IntTag) tag.getValue().get("x")).getValue(); + int y = ((IntTag) tag.getValue().get("y")).getValue(); + int z = ((IntTag) tag.getValue().get("z")).getValue(); NbtMapBuilder tagBuilder = getConstantBedrockTag(BlockEntityUtils.getBedrockBlockEntityId(id), x, y, z).toBuilder(); - Map translatedTags = translateTag(tag, blockState); - translatedTags.forEach(tagBuilder::put); + translateTag(tagBuilder, tag, blockState); return tagBuilder.build(); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CampfireBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CampfireBlockEntityTranslator.java index d6ac0281..3e4f9fb9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CampfireBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CampfireBlockEntityTranslator.java @@ -32,22 +32,16 @@ import com.nukkitx.nbt.NbtMapBuilder; import org.geysermc.connector.network.translators.item.ItemEntry; import org.geysermc.connector.network.translators.item.ItemRegistry; -import java.util.HashMap; -import java.util.Map; - @BlockEntity(name = "Campfire", regex = "campfire") public class CampfireBlockEntityTranslator extends BlockEntityTranslator { - @Override - public Map translateTag(CompoundTag tag, int blockState) { - Map tags = new HashMap<>(); + public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { ListTag items = tag.get("Items"); int i = 1; for (com.github.steveice10.opennbt.tag.builtin.Tag itemTag : items.getValue()) { - tags.put("Item" + i, getItem((CompoundTag) itemTag)); + builder.put("Item" + i, getItem((CompoundTag) itemTag)); i++; } - return tags; } protected NbtMap getItem(CompoundTag tag) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CommandBlockBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CommandBlockBlockEntityTranslator.java index 6bc940ad..2484dba7 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CommandBlockBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CommandBlockBlockEntityTranslator.java @@ -26,38 +26,33 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.opennbt.tag.builtin.*; +import com.nukkitx.nbt.NbtMapBuilder; import org.geysermc.connector.network.translators.world.block.BlockStateValues; import org.geysermc.connector.utils.MessageUtils; -import java.util.HashMap; -import java.util.Map; - @BlockEntity(name = "CommandBlock", regex = "command_block") public class CommandBlockBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { - @Override - public Map translateTag(CompoundTag tag, int blockState) { - Map map = new HashMap<>(); + public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { if (tag.size() < 5) { - return map; // These values aren't here + return; // These values aren't here } // Java infers from the block state, but Bedrock needs it in the tag - map.put("conditionalMode", BlockStateValues.getCommandBlockValues().getOrDefault(blockState, (byte) 0)); + builder.put("conditionalMode", BlockStateValues.getCommandBlockValues().getOrDefault(blockState, (byte) 0)); // Java and Bedrock values - map.put("conditionMet", ((ByteTag) tag.get("conditionMet")).getValue()); - map.put("auto", ((ByteTag) tag.get("auto")).getValue()); - map.put("CustomName", MessageUtils.getBedrockMessage(((StringTag) tag.get("CustomName")).getValue())); - map.put("powered", ((ByteTag) tag.get("powered")).getValue()); - map.put("Command", ((StringTag) tag.get("Command")).getValue()); - map.put("SuccessCount", ((IntTag) tag.get("SuccessCount")).getValue()); - map.put("TrackOutput", ((ByteTag) tag.get("TrackOutput")).getValue()); - map.put("UpdateLastExecution", ((ByteTag) tag.get("UpdateLastExecution")).getValue()); + builder.put("conditionMet", ((ByteTag) tag.get("conditionMet")).getValue()); + builder.put("auto", ((ByteTag) tag.get("auto")).getValue()); + builder.put("CustomName", MessageUtils.getBedrockMessage(((StringTag) tag.get("CustomName")).getValue())); + builder.put("powered", ((ByteTag) tag.get("powered")).getValue()); + builder.put("Command", ((StringTag) tag.get("Command")).getValue()); + builder.put("SuccessCount", ((IntTag) tag.get("SuccessCount")).getValue()); + builder.put("TrackOutput", ((ByteTag) tag.get("TrackOutput")).getValue()); + builder.put("UpdateLastExecution", ((ByteTag) tag.get("UpdateLastExecution")).getValue()); if (tag.get("LastExecution") != null) { - map.put("LastExecution", ((LongTag) tag.get("LastExecution")).getValue()); + builder.put("LastExecution", ((LongTag) tag.get("LastExecution")).getValue()); } else { - map.put("LastExecution", (long) 0); + builder.put("LastExecution", (long) 0); } - return map; } @Override diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/DoubleChestBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/DoubleChestBlockEntityTranslator.java index 5b59420e..47bcf489 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/DoubleChestBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/DoubleChestBlockEntityTranslator.java @@ -33,15 +33,11 @@ import org.geysermc.connector.network.translators.world.block.BlockStateValues; import org.geysermc.connector.network.translators.world.block.DoubleChestValue; import org.geysermc.connector.utils.BlockEntityUtils; -import java.util.HashMap; -import java.util.Map; - /** * Chests have more block entity properties in Bedrock, which is solved by implementing the BedrockOnlyBlockEntity */ @BlockEntity(name = "Chest", regex = "chest") public class DoubleChestBlockEntityTranslator extends BlockEntityTranslator implements BedrockOnlyBlockEntity, RequiresBlockState { - @Override public boolean isBlock(int blockState) { return BlockStateValues.getDoubleChestValues().containsKey(blockState); @@ -51,44 +47,39 @@ public class DoubleChestBlockEntityTranslator extends BlockEntityTranslator impl public void updateBlock(GeyserSession session, int blockState, Vector3i position) { CompoundTag javaTag = getConstantJavaTag("chest", position.getX(), position.getY(), position.getZ()); NbtMapBuilder tagBuilder = getConstantBedrockTag(BlockEntityUtils.getBedrockBlockEntityId("chest"), position.getX(), position.getY(), position.getZ()).toBuilder(); - translateTag(javaTag, blockState).forEach(tagBuilder::put); + translateTag(tagBuilder, javaTag, blockState); BlockEntityUtils.updateBlockEntity(session, tagBuilder.build(), position); } @Override - public Map translateTag(CompoundTag tag, int blockState) { - Map tags = new HashMap<>(); - if (BlockStateValues.getDoubleChestValues().containsKey(blockState)) { - DoubleChestValue chestValues = BlockStateValues.getDoubleChestValues().get(blockState); - if (chestValues != null) { - int x = (int) tag.getValue().get("x").getValue(); - int z = (int) tag.getValue().get("z").getValue(); - // Calculate the position of the other chest based on the Java block state - if (chestValues.isFacingEast) { - if (chestValues.isDirectionPositive) { - // East - z = z + (chestValues.isLeft ? 1 : -1); - } else { - // West - z = z + (chestValues.isLeft ? -1 : 1); - } + public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { + DoubleChestValue chestValues = BlockStateValues.getDoubleChestValues().getOrDefault(blockState, null); + if (chestValues != null) { + int x = (int) tag.getValue().get("x").getValue(); + int z = (int) tag.getValue().get("z").getValue(); + // Calculate the position of the other chest based on the Java block state + if (chestValues.isFacingEast) { + if (chestValues.isDirectionPositive) { + // East + z = z + (chestValues.isLeft ? 1 : -1); } else { - if (chestValues.isDirectionPositive) { - // South - x = x + (chestValues.isLeft ? -1 : 1); - } else { - // North - x = x + (chestValues.isLeft ? 1 : -1); - } + // West + z = z + (chestValues.isLeft ? -1 : 1); } - tags.put("pairx", x); - tags.put("pairz", z); - if (!chestValues.isLeft) { - tags.put("pairlead", (byte) 1); + } else { + if (chestValues.isDirectionPositive) { + // South + x = x + (chestValues.isLeft ? -1 : 1); + } else { + // North + x = x + (chestValues.isLeft ? 1 : -1); } } + builder.put("pairx", x); + builder.put("pairz", z); + if (!chestValues.isLeft) { + builder.put("pairlead", (byte) 1); + } } - return tags; } - } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EmptyBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EmptyBlockEntityTranslator.java index e9715bd3..3926b866 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EmptyBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EmptyBlockEntityTranslator.java @@ -26,16 +26,11 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; - -import java.util.HashMap; -import java.util.Map; +import com.nukkitx.nbt.NbtMapBuilder; @BlockEntity(name = "Empty", regex = "") public class EmptyBlockEntityTranslator extends BlockEntityTranslator { - @Override - public Map translateTag(CompoundTag tag, int blockState) { - return new HashMap<>(); + public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { } - } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EndGatewayBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EndGatewayBlockEntityTranslator.java index af94c560..0bf58822 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EndGatewayBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EndGatewayBlockEntityTranslator.java @@ -28,21 +28,18 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.IntTag; import com.nukkitx.nbt.NbtList; +import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.nbt.NbtType; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; -import java.util.HashMap; import java.util.LinkedHashMap; -import java.util.Map; @BlockEntity(name = "EndGateway", regex = "end_gateway") public class EndGatewayBlockEntityTranslator extends BlockEntityTranslator { - @Override - public Map translateTag(CompoundTag tag, int blockState) { - Map tags = new HashMap<>(); - tags.put("Age", (int) ((long) tag.get("Age").getValue())); + public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { + builder.put("Age", (int) ((long) tag.get("Age").getValue())); // Java sometimes does not provide this tag, but Bedrock crashes if it doesn't exist // Linked coordinates IntList tagsList = new IntArrayList(); @@ -50,8 +47,7 @@ public class EndGatewayBlockEntityTranslator extends BlockEntityTranslator { tagsList.add(getExitPortalCoordinate(tag, "X")); tagsList.add(getExitPortalCoordinate(tag, "Y")); tagsList.add(getExitPortalCoordinate(tag, "Z")); - tags.put("ExitPortal", new NbtList<>(NbtType.INT, tagsList)); - return tags; + builder.put("ExitPortal", new NbtList<>(NbtType.INT, tagsList)); } private int getExitPortalCoordinate(CompoundTag tag, String axis) { @@ -60,6 +56,7 @@ public class EndGatewayBlockEntityTranslator extends BlockEntityTranslator { LinkedHashMap compoundTag = (LinkedHashMap) tag.get("ExitPortal").getValue(); IntTag intTag = (IntTag) compoundTag.get(axis); return intTag.getValue(); - } return 0; + } + return 0; } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/FlowerPotBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/FlowerPotBlockEntityTranslator.java index 7bfcc7ee..f64474ae 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/FlowerPotBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/FlowerPotBlockEntityTranslator.java @@ -35,30 +35,19 @@ import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.utils.BlockEntityUtils; public class FlowerPotBlockEntityTranslator implements BedrockOnlyBlockEntity, RequiresBlockState { - - @Override - public boolean isBlock(int blockState) { - return (BlockStateValues.getFlowerPotValues().containsKey(blockState)); - } - - @Override - public void updateBlock(GeyserSession session, int blockState, Vector3i position) { - BlockEntityUtils.updateBlockEntity(session, getTag(blockState, position), position); - UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket(); - updateBlockPacket.setDataLayer(0); - updateBlockPacket.setRuntimeId(BlockTranslator.getBedrockBlockId(blockState)); - updateBlockPacket.setBlockPosition(position); - updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NEIGHBORS); - updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NETWORK); - updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.PRIORITY); - session.sendUpstreamPacket(updateBlockPacket); - BlockEntityUtils.updateBlockEntity(session, getTag(blockState, position), position); + /** + * @param blockState the Java block state of a potential flower pot block + * @return true if the block is a flower pot + */ + public static boolean isFlowerBlock(int blockState) { + return BlockStateValues.getFlowerPotValues().containsKey(blockState); } /** * Get the Nukkit CompoundTag of the flower pot. + * * @param blockState Java block state of flower pot. - * @param position Bedrock position of flower pot. + * @param position Bedrock position of flower pot. * @return Bedrock tag of flower pot. */ public static NbtMap getTag(int blockState, Vector3i position) { @@ -80,4 +69,23 @@ public class FlowerPotBlockEntityTranslator implements BedrockOnlyBlockEntity, R } return tagBuilder.build(); } + + @Override + public boolean isBlock(int blockState) { + return isFlowerBlock(blockState); + } + + @Override + public void updateBlock(GeyserSession session, int blockState, Vector3i position) { + BlockEntityUtils.updateBlockEntity(session, getTag(blockState, position), position); + UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket(); + updateBlockPacket.setDataLayer(0); + updateBlockPacket.setRuntimeId(BlockTranslator.getBedrockBlockId(blockState)); + updateBlockPacket.setBlockPosition(position); + updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NEIGHBORS); + updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NETWORK); + updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.PRIORITY); + session.sendUpstreamPacket(updateBlockPacket); + BlockEntityUtils.updateBlockEntity(session, getTag(blockState, position), position); + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/JigsawBlockBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/JigsawBlockBlockEntityTranslator.java index 43ac1a96..4fcdfe54 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/JigsawBlockBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/JigsawBlockBlockEntityTranslator.java @@ -27,21 +27,16 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.StringTag; - -import java.util.HashMap; -import java.util.Map; +import com.nukkitx.nbt.NbtMapBuilder; @BlockEntity(name = "JigsawBlock", regex = "jigsaw") public class JigsawBlockBlockEntityTranslator extends BlockEntityTranslator { - @Override - public Map translateTag(CompoundTag tag, int blockState) { - Map map = new HashMap<>(); - map.put("joint", ((StringTag) tag.get("joint")).getValue()); - map.put("name", ((StringTag) tag.get("name")).getValue()); - map.put("target_pool", ((StringTag) tag.get("pool")).getValue()); - map.put("final_state", ((StringTag) tag.get("final_state")).getValue()); - map.put("target", ((StringTag) tag.get("target")).getValue()); - return map; + 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()); + builder.put("final_state", ((StringTag) tag.get("final_state")).getValue()); + builder.put("target", ((StringTag) tag.get("target")).getValue()); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/NoteblockBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/NoteblockBlockEntityTranslator.java index f3e43009..fce0a056 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/NoteblockBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/NoteblockBlockEntityTranslator.java @@ -36,21 +36,20 @@ import org.geysermc.connector.utils.ChunkUtils; * Does not implement BlockEntityTranslator because it's only a block entity in Bedrock */ public class NoteblockBlockEntityTranslator implements RequiresBlockState { - @Override public boolean isBlock(int blockState) { return BlockStateValues.getNoteblockPitch(blockState) != -1; } public static void translate(GeyserSession session, Position position) { - int blockState = ChunkUtils.CACHED_BLOCK_ENTITIES.getOrDefault(position, 0); + int blockState = session.getConnector().getConfig().isCacheChunks() ? + session.getConnector().getWorldManager().getBlockAt(session, position) : + ChunkUtils.CACHED_BLOCK_ENTITIES.removeInt(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); - - ChunkUtils.CACHED_BLOCK_ENTITIES.remove(position); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/PistonBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/PistonBlockEntityTranslator.java index 54feacbe..c8a6e868 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/PistonBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/PistonBlockEntityTranslator.java @@ -34,9 +34,9 @@ import org.geysermc.connector.network.translators.world.block.BlockStateValues; * Pistons are a special case where they are only a block entity on Bedrock. */ public class PistonBlockEntityTranslator { - /** * Used in ChunkUtils to determine if the block is a piston. + * * @param blockState Java BlockState of block. * @return if block is a piston or not. */ @@ -46,8 +46,9 @@ public class PistonBlockEntityTranslator { /** * Calculates the Nukkit CompoundTag to send to the client on chunk + * * @param blockState Java block state of block. - * @param position Bedrock position of piston. + * @param position Bedrock position of piston. * @return Bedrock tag of piston. */ public static NbtMap getTag(int blockState, Vector3i position) { @@ -57,14 +58,13 @@ public class PistonBlockEntityTranslator { .putInt("z", position.getZ()) .putByte("isMovable", (byte) 1) .putString("id", "PistonArm"); - if (BlockStateValues.getPistonValues().containsKey(blockState)) { - boolean extended = BlockStateValues.getPistonValues().get(blockState); - // 1f if extended, otherwise 0f - tagBuilder.putFloat("Progress", (extended) ? 1.0f : 0.0f); - // 1 if sticky, 0 if not - tagBuilder.putByte("Sticky", (byte)((BlockStateValues.isStickyPiston(blockState)) ? 1 : 0)); - } + + boolean extended = BlockStateValues.getPistonValues().get(blockState); + // 1f if extended, otherwise 0f + tagBuilder.putFloat("Progress", (extended) ? 1.0f : 0.0f); + // 1 if sticky, 0 if not + tagBuilder.putByte("Sticky", (byte) ((BlockStateValues.isStickyPiston(blockState)) ? 1 : 0)); + return tagBuilder.build(); } - } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/ShulkerBoxBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/ShulkerBoxBlockEntityTranslator.java index 08e3abaa..69fa1084 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/ShulkerBoxBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/ShulkerBoxBlockEntityTranslator.java @@ -26,23 +26,18 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.nukkitx.nbt.NbtMapBuilder; import org.geysermc.connector.network.translators.world.block.BlockStateValues; -import java.util.HashMap; -import java.util.Map; - @BlockEntity(name = "ShulkerBox", regex = "shulker_box") public class ShulkerBoxBlockEntityTranslator extends BlockEntityTranslator { - @Override - public Map translateTag(CompoundTag tag, int blockState) { - Map tags = new HashMap<>(); - + public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { byte direction = BlockStateValues.getShulkerBoxDirection(blockState); // Just in case... - if (direction == -1) direction = 1; - tags.put("facing", direction); - return tags; + if (direction == -1) { + direction = 1; + } + builder.put("facing", direction); } - } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java index b40ed42c..a4f800da 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java @@ -27,51 +27,12 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.mc.protocol.data.message.MessageSerializer; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.nukkitx.nbt.NbtMapBuilder; import org.geysermc.connector.utils.MessageUtils; import org.geysermc.connector.utils.SignUtils; -import java.util.HashMap; -import java.util.Map; - @BlockEntity(name = "Sign", regex = "sign") public class SignBlockEntityTranslator extends BlockEntityTranslator { - - @Override - public Map translateTag(CompoundTag tag, int blockState) { - Map tags = new HashMap<>(); - - StringBuilder signText = new StringBuilder(); - for(int i = 0; i < 4; i++) { - int currentLine = i+1; - String signLine = getOrDefault(tag.getValue().get("Text" + currentLine), ""); - signLine = MessageUtils.getBedrockMessage(MessageSerializer.fromString(signLine)); - - // Check the character width on the sign to ensure there is no overflow that is usually hidden - // to Java Edition clients but will appear to Bedrock clients - int signWidth = 0; - StringBuilder finalSignLine = new StringBuilder(); - for (char c : signLine.toCharArray()) { - signWidth += SignUtils.getCharacterWidth(c); - if (signWidth <= SignUtils.BEDROCK_CHARACTER_WIDTH_MAX) { - finalSignLine.append(c); - } else { - break; - } - } - - // Java Edition 1.14 added the ability to change the text color of the whole sign using dye - if (tag.contains("Color")) { - signText.append(getBedrockSignColor(tag.get("Color").getValue().toString())); - } - - signText.append(finalSignLine.toString()); - signText.append("\n"); - } - - tags.put("Text", MessageUtils.getBedrockMessage(MessageSerializer.fromString(signText.toString()))); - return tags; - } - /** * Maps a color stored in a sign's Color tag to a Bedrock Edition formatting code. *
@@ -133,4 +94,36 @@ public class SignBlockEntityTranslator extends BlockEntityTranslator { return base; } + @Override + public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { + StringBuilder signText = new StringBuilder(); + for (int i = 0; i < 4; i++) { + int currentLine = i + 1; + String signLine = getOrDefault(tag.getValue().get("Text" + currentLine), ""); + signLine = MessageUtils.getBedrockMessage(MessageSerializer.fromString(signLine)); + + // Check the character width on the sign to ensure there is no overflow that is usually hidden + // to Java Edition clients but will appear to Bedrock clients + int signWidth = 0; + StringBuilder finalSignLine = new StringBuilder(); + for (char c : signLine.toCharArray()) { + signWidth += SignUtils.getCharacterWidth(c); + if (signWidth <= SignUtils.BEDROCK_CHARACTER_WIDTH_MAX) { + finalSignLine.append(c); + } else { + break; + } + } + + // Java Edition 1.14 added the ability to change the text color of the whole sign using dye + if (tag.contains("Color")) { + signText.append(getBedrockSignColor(tag.get("Color").getValue().toString())); + } + + signText.append(finalSignLine.toString()); + signText.append("\n"); + } + + builder.put("Text", MessageUtils.getBedrockMessage(MessageSerializer.fromString(signText.toString()))); + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java index 6d350c0c..c5f47994 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java @@ -25,29 +25,26 @@ package org.geysermc.connector.network.translators.world.block.entity; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.nukkitx.nbt.NbtMapBuilder; import org.geysermc.connector.network.translators.world.block.BlockStateValues; -import java.util.HashMap; -import java.util.Map; - @BlockEntity(name = "Skull", regex = "skull") public class SkullBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { - @Override public boolean isBlock(int blockState) { return BlockStateValues.getSkullVariant(blockState) != -1; } @Override - public Map translateTag(com.github.steveice10.opennbt.tag.builtin.CompoundTag tag, int blockState) { - Map tags = new HashMap<>(); + public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { byte skullVariant = BlockStateValues.getSkullVariant(blockState); float rotation = BlockStateValues.getSkullRotation(blockState) * 22.5f; // Just in case... - if (skullVariant == -1) skullVariant = 0; - tags.put("Rotation", rotation); - tags.put("SkullType", skullVariant); - return tags; + if (skullVariant == -1) { + skullVariant = 0; + } + builder.put("Rotation", rotation); + builder.put("SkullType", skullVariant); } - } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SpawnerBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SpawnerBlockEntityTranslator.java index 2601e3de..38507f54 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SpawnerBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SpawnerBlockEntityTranslator.java @@ -26,63 +26,58 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; +import com.nukkitx.nbt.NbtMapBuilder; import org.geysermc.connector.entity.type.EntityType; -import java.util.HashMap; -import java.util.Map; - @BlockEntity(name = "MobSpawner", regex = "mob_spawner") public class SpawnerBlockEntityTranslator extends BlockEntityTranslator { - @Override - public Map translateTag(CompoundTag tag, int blockState) { - Map tags = new HashMap<>(); + public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { + Tag current; - if (tag.get("MaxNearbyEntities") != null) { - tags.put("MaxNearbyEntities", (short) tag.get("MaxNearbyEntities").getValue()); + if ((current = tag.get("MaxNearbyEntities")) != null) { + builder.put("MaxNearbyEntities", current.getValue()); } - if (tag.get("RequiredPlayerRange") != null) { - tags.put("RequiredPlayerRange", (short) tag.get("RequiredPlayerRange").getValue()); + if ((current = tag.get("RequiredPlayerRange")) != null) { + builder.put("RequiredPlayerRange", current.getValue()); } - if (tag.get("SpawnCount") != null) { - tags.put("SpawnCount", (short) tag.get("SpawnCount").getValue()); + if ((current = tag.get("SpawnCount")) != null) { + builder.put("SpawnCount", current.getValue()); } - if (tag.get("MaxSpawnDelay") != null) { - tags.put("MaxSpawnDelay", (short) tag.get("MaxSpawnDelay").getValue()); + if ((current = tag.get("MaxSpawnDelay")) != null) { + builder.put("MaxSpawnDelay", current.getValue()); } - if (tag.get("Delay") != null) { - tags.put("Delay", (short) tag.get("Delay").getValue()); + if ((current = tag.get("Delay")) != null) { + builder.put("Delay", current.getValue()); } - if (tag.get("SpawnRange") != null) { - tags.put("SpawnRange", (short) tag.get("SpawnRange").getValue()); + if ((current = tag.get("SpawnRange")) != null) { + builder.put("SpawnRange", current.getValue()); } - if (tag.get("MinSpawnDelay") != null) { - tags.put("MinSpawnDelay", (short) tag.get("MinSpawnDelay").getValue()); + if ((current = tag.get("MinSpawnDelay")) != null) { + builder.put("MinSpawnDelay", current.getValue()); } - if (tag.get("SpawnData") != null) { - CompoundTag spawnData = tag.get("SpawnData"); + CompoundTag spawnData = tag.get("SpawnData"); + if (spawnData != null) { String entityID = (String) spawnData.get("id").getValue(); - tags.put("EntityIdentifier", entityID); + builder.put("EntityIdentifier", entityID); EntityType type = EntityType.getFromIdentifier(entityID); if (type != null) { - tags.put("DisplayEntityWidth", type.getWidth()); - tags.put("DisplayEntityHeight", type.getHeight()); - tags.put("DisplayEntityScale", 1.0f); + builder.put("DisplayEntityWidth", type.getWidth()); + builder.put("DisplayEntityHeight", type.getHeight()); + builder.put("DisplayEntityScale", 1.0f); } } - tags.put("id", "MobSpawner"); - tags.put("isMovable", (byte) 1); - - return tags; + builder.put("id", "MobSpawner"); + builder.put("isMovable", (byte) 1); } - } diff --git a/connector/src/main/java/org/geysermc/connector/utils/BlockEntityUtils.java b/connector/src/main/java/org/geysermc/connector/utils/BlockEntityUtils.java index 0ae31b33..e8fd8291 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/BlockEntityUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/BlockEntityUtils.java @@ -33,17 +33,17 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.block.entity.BlockEntityTranslator; public class BlockEntityUtils { - private static final BlockEntityTranslator EMPTY_TRANSLATOR = BlockEntityTranslator.BLOCK_ENTITY_TRANSLATORS.get("Empty"); public static String getBedrockBlockEntityId(String id) { // These are the only exceptions when it comes to block entity ids - if (BlockEntityTranslator.BLOCK_ENTITY_TRANSLATIONS.containsKey(id)) { - return BlockEntityTranslator.BLOCK_ENTITY_TRANSLATIONS.get(id); + String value = BlockEntityTranslator.BLOCK_ENTITY_TRANSLATIONS.get(id); + if (value != null) { + return value; } id = id.replace("minecraft:", "") - .replace("_", " "); + .replace("_", " "); // Split at every space or capital letter - for the latter, some legacy Java block entity tags are the correct format already String[] words; if (!id.toUpperCase().equals(id)) { // Otherwise we get [S, K, U, L, L] @@ -60,11 +60,10 @@ public class BlockEntityUtils { public static BlockEntityTranslator getBlockEntityTranslator(String name) { BlockEntityTranslator blockEntityTranslator = BlockEntityTranslator.BLOCK_ENTITY_TRANSLATORS.get(name); - if (blockEntityTranslator == null) { - return EMPTY_TRANSLATOR; + if (blockEntityTranslator != null) { + return blockEntityTranslator; } - - return blockEntityTranslator; + return EMPTY_TRANSLATOR; } public static void updateBlockEntity(GeyserSession session, NbtMap blockEntity, Position position) { diff --git a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java index a63eeb42..0769a4d1 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java @@ -72,9 +72,9 @@ import static org.geysermc.connector.network.translators.world.block.BlockTransl @UtilityClass public class ChunkUtils { - /** - * Temporarily stores positions of BlockState values that are needed for certain block entities actively + * Temporarily stores positions of BlockState values that are needed for certain block entities actively. + * Not used if cache chunks is enabled */ public static final Object2IntMap CACHED_BLOCK_ENTITIES = new Object2IntOpenHashMap<>(); @@ -300,11 +300,16 @@ public class ChunkUtils { public static void updateBlock(GeyserSession session, int blockState, Vector3i position) { // Checks for item frames so they aren't tripped up and removed - if (ItemFrameEntity.positionContainsItemFrame(session, position) && blockState == AIR) { - ((ItemFrameEntity) session.getEntityCache().getEntityByJavaId(ItemFrameEntity.getItemFrameEntityId(session, position))).updateBlock(session); - return; - } else if (ItemFrameEntity.positionContainsItemFrame(session, position)) { - Entity entity = session.getEntityCache().getEntityByJavaId(ItemFrameEntity.getItemFrameEntityId(session, position)); + long frameEntityId = ItemFrameEntity.getItemFrameEntityId(session, position); + if (frameEntityId != -1) { + // TODO: Very occasionally the item frame doesn't sync up when destroyed + Entity entity = session.getEntityCache().getEntityByJavaId(frameEntityId); + if (blockState == AIR && entity != null) { // Item frame is still present and no block overrides that; refresh it + ((ItemFrameEntity) entity).updateBlock(session); + return; + } + + // Otherwise the item frame is gone if (entity != null) { session.getEntityCache().removeEntity(entity, false); } else { @@ -342,7 +347,10 @@ public class ChunkUtils { ((BedrockOnlyBlockEntity) requiresBlockState).updateBlock(session, blockState, position); break; } - CACHED_BLOCK_ENTITIES.put(new Position(position.getX(), position.getY(), position.getZ()), blockState); + if (!session.getConnector().getConfig().isCacheChunks()) { + // Blocks aren't saved to a chunk cache; resort to this smaller cache + CACHED_BLOCK_ENTITIES.put(new Position(position.getX(), position.getY(), position.getZ()), blockState); + } break; //No block will be a part of two classes } } From 0e15aa7441eff940c8b5f0bbb475eb46b5c0af15 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Thu, 5 Nov 2020 18:42:33 -0500 Subject: [PATCH 027/116] Fireball and ghast improvements (#1469) * Fireball and ghast improvements - Ghasts now visually show if they're charging a fireball - Fireballs are now vastly better and will update better * Add gravity and drag to projectiles * Add check for session close and improve fireball * Remove motion stuff from fireball * Make fireball hittable * Add wither skull entity * Small changes * Add note about laggy fireballs Co-authored-by: David Choo --- .../entity/ItemedFireballEntity.java | 18 +++- .../connector/entity/ThrowableEntity.java | 97 ++++++++++++++++--- .../connector/entity/WitherSkullEntity.java | 58 +++++++++++ .../entity/living/monster/GhastEntity.java | 49 ++++++++++ .../connector/entity/type/EntityType.java | 6 +- 5 files changed, 212 insertions(+), 16 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/entity/WitherSkullEntity.java create mode 100644 connector/src/main/java/org/geysermc/connector/entity/living/monster/GhastEntity.java diff --git a/connector/src/main/java/org/geysermc/connector/entity/ItemedFireballEntity.java b/connector/src/main/java/org/geysermc/connector/entity/ItemedFireballEntity.java index e04e0411..1544f767 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ItemedFireballEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/ItemedFireballEntity.java @@ -27,10 +27,24 @@ package org.geysermc.connector.entity; import com.nukkitx.math.vector.Vector3f; import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; -public class ItemedFireballEntity extends Entity { +public class ItemedFireballEntity extends ThrowableEntity { + private final Vector3f acceleration; public ItemedFireballEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); + super(entityId, geyserId, entityType, position, Vector3f.ZERO, rotation); + acceleration = motion; + } + + @Override + protected void updatePosition(GeyserSession session) { + position = position.add(motion); + // TODO: While this reduces latency in position updating (needed for better fireball reflecting), + // TODO: movement is incredibly stiff. See if the MoveEntityDeltaPacket in 1.16.100 fixes this, and if not, + // TODO: only use this laggy movement for fireballs that be reflected + moveAbsoluteImmediate(session, position, rotation, false, true); + float drag = getDrag(session); + motion = motion.add(acceleration).mul(drag); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java b/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java index b3632606..5b7ba5c0 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java @@ -31,14 +31,23 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +/** + * Used as a class for any object-like entity that moves as a projectile + */ public class ThrowableEntity extends Entity { private Vector3f lastPosition; - private ScheduledFuture positionUpdater; + /** + * Updates the position for the Bedrock client. + * + * Java clients assume the next positions of moving items. Bedrock needs to be explicitly told positions + */ + protected ScheduledFuture positionUpdater; public ThrowableEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); @@ -49,20 +58,86 @@ public class ThrowableEntity extends Entity { public void spawnEntity(GeyserSession session) { super.spawnEntity(session); positionUpdater = session.getConnector().getGeneralThreadPool().scheduleAtFixedRate(() -> { - super.moveRelative(session, motion.getX(), motion.getY(), motion.getZ(), rotation, onGround); - - if (metadata.getFlags().getFlag(EntityFlag.HAS_GRAVITY)) { - float gravity = 0.03f; // Snowball, Egg, and Ender Pearl - if (entityType == EntityType.THROWN_POTION || entityType == EntityType.LINGERING_POTION) { - gravity = 0.05f; - } else if (entityType == EntityType.THROWN_EXP_BOTTLE) { - gravity = 0.07f; - } - motion = motion.down(gravity); + if (session.isClosed()) { + positionUpdater.cancel(true); + return; } + updatePosition(session); }, 0, 50, TimeUnit.MILLISECONDS); } + protected void moveAbsoluteImmediate(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) { + super.moveAbsolute(session, position, rotation, isOnGround, teleported); + } + + protected void updatePosition(GeyserSession session) { + super.moveRelative(session, motion.getX(), motion.getY(), motion.getZ(), rotation, onGround); + float drag = getDrag(session); + float gravity = getGravity(); + motion = motion.mul(drag).down(gravity); + } + + /** + * Get the gravity of this entity type. Used for applying gravity while the entity is in motion. + * + * @return the amount of gravity to apply to this entity while in motion. + */ + protected float getGravity() { + if (metadata.getFlags().getFlag(EntityFlag.HAS_GRAVITY)) { + switch (entityType) { + case THROWN_POTION: + case LINGERING_POTION: + return 0.05f; + case THROWN_EXP_BOTTLE: + return 0.07f; + case FIREBALL: + return 0; + case SNOWBALL: + case THROWN_EGG: + case THROWN_ENDERPEARL: + return 0.03f; + } + } + return 0; + } + + /** + * @param session the session of the Bedrock client. + * @return the drag that should be multiplied to the entity's motion + */ + protected float getDrag(GeyserSession session) { + if (isInWater(session)) { + return 0.8f; + } else { + switch (entityType) { + case THROWN_POTION: + case LINGERING_POTION: + case THROWN_EXP_BOTTLE: + case SNOWBALL: + case THROWN_EGG: + case THROWN_ENDERPEARL: + return 0.99f; + case FIREBALL: + case SMALL_FIREBALL: + case DRAGON_FIREBALL: + return 0.95f; + } + } + return 1; + } + + /** + * @param session the session of the Bedrock client. + * @return true if this entity is currently in water. + */ + protected boolean isInWater(GeyserSession session) { + if (session.getConnector().getConfig().isCacheChunks()) { + int block = session.getConnector().getWorldManager().getBlockAt(session, position.toInt()); + return block == BlockTranslator.BEDROCK_WATER_ID; + } + return false; + } + @Override public boolean despawnEntity(GeyserSession session) { positionUpdater.cancel(true); diff --git a/connector/src/main/java/org/geysermc/connector/entity/WitherSkullEntity.java b/connector/src/main/java/org/geysermc/connector/entity/WitherSkullEntity.java new file mode 100644 index 00000000..99b3df3d --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/WitherSkullEntity.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2019-2020 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.connector.entity; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class WitherSkullEntity extends ItemedFireballEntity { + private boolean isCharged; + + public WitherSkullEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + protected float getDrag(GeyserSession session) { + return isCharged ? 0.73f : super.getDrag(session); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 7) { + boolean newIsCharged = (boolean) entityMetadata.getValue(); + if (newIsCharged != isCharged) { + isCharged = newIsCharged; + entityType = isCharged ? EntityType.WITHER_SKULL_DANGEROUS : EntityType.WITHER_SKULL; + despawnEntity(session); + spawnEntity(session); + } + } + super.updateBedrockMetadata(entityMetadata, session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/GhastEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/GhastEntity.java new file mode 100644 index 00000000..3d3be87c --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/GhastEntity.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2019-2020 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.connector.entity.living.monster; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import org.geysermc.connector.entity.living.FlyingEntity; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class GhastEntity extends FlyingEntity { + + public GhastEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 15) { + // If the ghast is attacking + metadata.put(EntityData.CHARGE_AMOUNT, (byte) ((boolean) entityMetadata.getValue() ? 1 : 0)); + } + super.updateBedrockMetadata(entityMetadata, session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java index fddab5a4..7557bd04 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java +++ b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java @@ -74,7 +74,7 @@ public enum EntityType { ENDERMAN(EndermanEntity.class, 38, 2.9f, 0.6f), SILVERFISH(MonsterEntity.class, 39, 0.3f, 0.4f), CAVE_SPIDER(MonsterEntity.class, 40, 0.5f, 0.7f), - GHAST(FlyingEntity.class, 41, 4.0f), + GHAST(GhastEntity.class, 41, 4.0f), MAGMA_CUBE(MagmaCubeEntity.class, 42, 0.51f), BLAZE(BlazeEntity.class, 43, 1.8f, 0.6f), ZOMBIE_VILLAGER(ZombieEntity.class, 44, 1.8f, 0.6f, 0.6f, 1.62f), @@ -125,9 +125,9 @@ public enum EntityType { THROWN_POTION(ThrowableEntity.class, 86, 0.25f, 0.25f, 0.25f, 0f, "minecraft:splash_potion"), THROWN_ENDERPEARL(ThrowableEntity.class, 87, 0.25f, 0.25f, 0.25f, 0f, "minecraft:ender_pearl"), LEASH_KNOT(LeashKnotEntity.class, 88, 0.5f, 0.375f), - WITHER_SKULL(Entity.class, 89, 0.3125f), + WITHER_SKULL(WitherSkullEntity.class, 89, 0.3125f), BOAT(BoatEntity.class, 90, 0.7f, 1.6f, 1.6f, 0.35f), - WITHER_SKULL_DANGEROUS(Entity.class, 91, 0f), + WITHER_SKULL_DANGEROUS(WitherSkullEntity.class, 91, 0f), LIGHTNING_BOLT(Entity.class, 93, 0f), SMALL_FIREBALL(ItemedFireballEntity.class, 94, 0.3125f), AREA_EFFECT_CLOUD(AreaEffectCloudEntity.class, 95, 0.5f, 1.0f), From a6cc28ee80072b57b4f6793cc22a6ad6bfd8c90c Mon Sep 17 00:00:00 2001 From: rtm516 Date: Sat, 7 Nov 2020 00:52:09 +0000 Subject: [PATCH 028/116] Add config option for enabling achievements (#1504) * Add config option for enabling achievements * Disabled achievements by default and added warning about commands being disabled * Update config.yml * Rename achievements-enabled to xbox-achievements-enabled for clarity Co-authored-by: Camotoy <20743703+DoctorMacc@users.noreply.github.com> --- .../connector/configuration/GeyserConfiguration.java | 2 ++ .../connector/configuration/GeyserJacksonConfiguration.java | 3 +++ .../geysermc/connector/network/session/GeyserSession.java | 4 ++-- connector/src/main/resources/config.yml | 5 +++++ 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java b/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java index c1cc4d03..153a9174 100644 --- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java +++ b/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java @@ -81,6 +81,8 @@ public interface GeyserConfiguration { boolean isForceResourcePacks(); + boolean isXboxAchievementsEnabled(); + int getCacheImages(); IMetricsInfo getMetrics(); diff --git a/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java b/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java index 45676fbd..d19cfe49 100644 --- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java +++ b/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java @@ -107,6 +107,9 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration @JsonProperty("force-resource-packs") private boolean forceResourcePacks = true; + @JsonProperty("xbox-achievements-enabled") + private boolean xboxAchievementsEnabled = false; + private MetricsInfo metrics = new MetricsInfo(); @Getter diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 6a2913c8..4429268a 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -634,7 +634,7 @@ public class GeyserSession implements CommandSender { startGamePacket.setLevelGameType(GameType.SURVIVAL); startGamePacket.setDifficulty(1); startGamePacket.setDefaultSpawn(Vector3i.ZERO); - startGamePacket.setAchievementsDisabled(true); + startGamePacket.setAchievementsDisabled(!connector.getConfig().isXboxAchievementsEnabled()); startGamePacket.setCurrentTick(-1); startGamePacket.setEduEditionOffers(0); startGamePacket.setEduFeaturesEnabled(false); @@ -645,7 +645,7 @@ public class GeyserSession implements CommandSender { startGamePacket.getGamerules().add(new GameRuleData<>("showcoordinates", true)); startGamePacket.setPlatformBroadcastMode(GamePublishSetting.PUBLIC); startGamePacket.setXblBroadcastMode(GamePublishSetting.PUBLIC); - startGamePacket.setCommandsEnabled(true); + startGamePacket.setCommandsEnabled(!connector.getConfig().isXboxAchievementsEnabled()); startGamePacket.setTexturePacksRequired(false); startGamePacket.setBonusChestEnabled(false); startGamePacket.setStartingWithMap(false); diff --git a/connector/src/main/resources/config.yml b/connector/src/main/resources/config.yml index 43e3e8ed..b53fcdd0 100644 --- a/connector/src/main/resources/config.yml +++ b/connector/src/main/resources/config.yml @@ -117,6 +117,11 @@ above-bedrock-nether-building: false # want to download the resource packs force-resource-packs: true +# Allows Xbox achievements to be unlocked. +# This disables certain commands so the Bedrock client can't to "cheat" to get them. +# Commands such as /gamemode and /give will not work from Bedrock with this enabled +xbox-achievements-enabled: false + # bStats is a stat tracker that is entirely anonymous and tracks only basic information # about Geyser, such as how many people are online, how many servers are using Geyser, # what OS is being used, etc. You can learn more about bStats here: https://bstats.org/. From c7654ff98c3e25bf0e273b513f4a0b37088d0e31 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Sat, 7 Nov 2020 18:47:45 +0000 Subject: [PATCH 029/116] Disable the sponsor button (#1509) --- .github/FUNDING.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index b4b93dca..19b655c2 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,7 +1,7 @@ # These are supported funding model platforms github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] -patreon: GeyserMC +patreon: #GeyserMC # Disabled currently open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel From 2d95302b1014ef53357c0f3e29cf91ca95634c1d Mon Sep 17 00:00:00 2001 From: rtm516 Date: Sat, 7 Nov 2020 19:17:17 +0000 Subject: [PATCH 030/116] Add support for passing config options as arguments (#1506) --- .../standalone/GeyserStandaloneBootstrap.java | 151 +++++++++++++++++- connector/src/main/resources/languages | 2 +- 2 files changed, 149 insertions(+), 4 deletions(-) diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java index 123a9a60..f58a997e 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java @@ -25,6 +25,11 @@ package org.geysermc.platform.standalone; +import com.fasterxml.jackson.databind.BeanDescription; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.introspect.AnnotatedField; +import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition; import lombok.Getter; import net.minecrell.terminalconsole.TerminalConsoleAppender; import org.apache.logging.log4j.LogManager; @@ -36,6 +41,7 @@ import org.geysermc.connector.bootstrap.GeyserBootstrap; import org.geysermc.connector.command.CommandManager; import org.geysermc.connector.common.PlatformType; import org.geysermc.connector.configuration.GeyserConfiguration; +import org.geysermc.connector.configuration.GeyserJacksonConfiguration; import org.geysermc.connector.dump.BootstrapDumpInfo; import org.geysermc.connector.ping.GeyserLegacyPingPassthrough; import org.geysermc.connector.ping.IGeyserPingPassthrough; @@ -50,7 +56,8 @@ import java.lang.reflect.Method; import java.nio.file.Path; import java.nio.file.Paths; import java.text.MessageFormat; -import java.util.UUID; +import java.util.*; +import java.util.stream.Collectors; public class GeyserStandaloneBootstrap implements GeyserBootstrap { @@ -67,6 +74,9 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { private GeyserConnector connector; + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + private static final Map argsConfigKeys = new HashMap<>(); public static void main(String[] args) { GeyserStandaloneBootstrap bootstrap = new GeyserStandaloneBootstrap(); @@ -74,6 +84,8 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { boolean useGuiOpts = bootstrap.useGui; String configFilenameOpt = bootstrap.configFilename; + List availableProperties = getPOJOForClass(GeyserJacksonConfiguration.class); + for (int i = 0; i < args.length; i++) { // By default, standalone Geyser will check if it should open the GUI based on if the GUI is null // Optionally, you can force the use of a GUI or no GUI by specifying args @@ -106,8 +118,43 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { System.out.println(" --gui, --nogui " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.gui")); return; default: - String badArgMsg = LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.unrecognised"); - System.err.println(MessageFormat.format(badArgMsg, arg)); + // We have likely added a config option argument + if (arg.startsWith("--")) { + // Split the argument by an = + String[] argParts = arg.substring(2).split("="); + if (argParts.length == 2) { + // Split the config key by . to allow for nested options + String[] configKeyParts = argParts[0].split("\\."); + + // Loop the possible config options to check the passed key is valid + boolean found = false; + for (BeanPropertyDefinition property : availableProperties) { + if (configKeyParts[0].equals(property.getName())) { + if (configKeyParts.length > 1) { + // Loop sub-section options to check the passed key is valid + for (BeanPropertyDefinition subProperty : getPOJOForClass(property.getRawPrimaryType())) { + if (configKeyParts[1].equals(subProperty.getName())) { + found = true; + break; + } + } + } else { + found = true; + } + + break; + } + } + + // Add the found key to the stored list for later usage + if (found) { + argsConfigKeys.put(argParts[0], argParts[1]); + break; + } + } + } + + System.err.println(LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.unrecognised", arg)); return; } } @@ -148,6 +195,9 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { try { File configFile = FileUtils.fileOrCopiedFromResource(new File(configFilename), "config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString())); geyserConfig = FileUtils.loadConfig(configFile, GeyserStandaloneConfiguration.class); + + handleArgsConfigOptions(); + if (this.geyserConfig.getRemote().getAddress().equalsIgnoreCase("auto")) { geyserConfig.setAutoconfiguredRemote(true); // Doesn't really need to be set but /shrug geyserConfig.getRemote().setAddress("127.0.0.1"); @@ -223,4 +273,99 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { public BootstrapDumpInfo getDumpInfo() { return new GeyserStandaloneDumpInfo(this); } + + /** + * Get the {@link BeanPropertyDefinition}s for the given class + * + * @param clazz The class to get the definitions for + * @return A list of {@link BeanPropertyDefinition} for the given class + */ + public static List getPOJOForClass(Class clazz) { + JavaType javaType = OBJECT_MAPPER.getTypeFactory().constructType(clazz); + + // Introspect the given type + BeanDescription beanDescription = OBJECT_MAPPER.getSerializationConfig().introspect(javaType); + + // Find properties + List properties = beanDescription.findProperties(); + + // Get the ignored properties + Set ignoredProperties = OBJECT_MAPPER.getSerializationConfig().getAnnotationIntrospector() + .findPropertyIgnorals(beanDescription.getClassInfo()).getIgnored(); + + // Filter properties removing the ignored ones + return properties.stream() + .filter(property -> !ignoredProperties.contains(property.getName())) + .collect(Collectors.toList()); + } + + /** + * Set a POJO property value on an object + * + * @param property The {@link BeanPropertyDefinition} to set + * @param parentObject The object to alter + * @param value The new value of the property + */ + private static void setConfigOption(BeanPropertyDefinition property, Object parentObject, Object value) { + Object parsedValue = value; + + // Change the values type if needed + if (int.class.equals(property.getRawPrimaryType())) { + parsedValue = Integer.valueOf((String) parsedValue); + } else if (boolean.class.equals(property.getRawPrimaryType())) { + parsedValue = Boolean.valueOf((String) parsedValue); + } + + // Force the value to be set + AnnotatedField field = property.getField(); + field.fixAccess(true); + field.setValue(parentObject, parsedValue); + } + + /** + * Update the loaded {@link GeyserStandaloneConfiguration} with any values passed in the command line arguments + */ + private void handleArgsConfigOptions() { + // Get the available properties from the class + List availableProperties = getPOJOForClass(GeyserJacksonConfiguration.class); + + for (Map.Entry configKey : argsConfigKeys.entrySet()) { + String[] configKeyParts = configKey.getKey().split("\\."); + + // Loop over the properties looking for any matches against the stored one from the argument + for (BeanPropertyDefinition property : availableProperties) { + if (configKeyParts[0].equals(property.getName())) { + if (configKeyParts.length > 1) { + // Loop through the sub property if the first part matches + for (BeanPropertyDefinition subProperty : getPOJOForClass(property.getRawPrimaryType())) { + if (configKeyParts[1].equals(subProperty.getName())) { + geyserLogger.info(LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.set_config_option", configKey.getKey(), configKey.getValue())); + + // Set the sub property value on the config + try { + Object subConfig = property.getGetter().callOn(geyserConfig); + setConfigOption(subProperty, subConfig, configKey.getValue()); + } catch (Exception e) { + geyserLogger.error("Failed to set config option: " + property.getFullName()); + } + + break; + } + } + } else { + geyserLogger.info(LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.set_config_option", configKey.getKey(), configKey.getValue())); + + // Set the property value on the config + try { + setConfigOption(property, geyserConfig, configKey.getValue()); + } catch (Exception e) { + geyserLogger.error("Failed to set config option: " + property.getFullName()); + } + } + + break; + } + } + } + } } diff --git a/connector/src/main/resources/languages b/connector/src/main/resources/languages index b7ef31bd..bf4b0b71 160000 --- a/connector/src/main/resources/languages +++ b/connector/src/main/resources/languages @@ -1 +1 @@ -Subproject commit b7ef31bd9c45aa3a0735883764c231f30cb55bfa +Subproject commit bf4b0b7103193154dd0b06e0459dc375c753069a From e00715ceabf9246cbfc63e72a559a1953f5fc156 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Mon, 9 Nov 2020 10:34:27 +0100 Subject: [PATCH 031/116] Fixed some issues related to Scoreboards (#1446) * Fixes some issues related to Scoreboard Teams * The cached Score info should update no matter what kind of update the Team got. * Team entities specified at the create Team packet should also be checked if they exist as Score in the registered Objectives * Rewrote some Scoreboard code and fixed various issues * Minor formatting changes --- .../connector/entity/PlayerEntity.java | 17 +- .../JavaDisplayScoreboardTranslator.java | 5 +- .../JavaScoreboardObjectiveTranslator.java | 17 +- .../connector/scoreboard/Objective.java | 62 ++++--- .../geysermc/connector/scoreboard/Score.java | 99 +++++++++--- .../connector/scoreboard/Scoreboard.java | 135 +++++++++------- .../geysermc/connector/scoreboard/Team.java | 152 +++++++++++++++--- 7 files changed, 333 insertions(+), 154 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java index 390110d1..bfadd83c 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java @@ -27,7 +27,6 @@ package org.geysermc.connector.entity; 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.scoreboard.NameTagVisibility; import com.github.steveice10.mc.protocol.data.message.TextMessage; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.math.vector.Vector3f; @@ -235,18 +234,12 @@ public class PlayerEntity extends LivingEntity { } Team team = session.getWorldCache().getScoreboard().getTeamFor(username); if (team != null) { - // Cover different visibility settings - if (team.getNameTagVisibility() == NameTagVisibility.NEVER) { - metadata.put(EntityData.NAMETAG, ""); - } else if (team.getNameTagVisibility() == NameTagVisibility.HIDE_FOR_OTHER_TEAMS && - !team.getEntities().contains(session.getPlayerEntity().getUsername())) { - metadata.put(EntityData.NAMETAG, ""); - } else if (team.getNameTagVisibility() == NameTagVisibility.HIDE_FOR_OWN_TEAM && - team.getEntities().contains(session.getPlayerEntity().getUsername())) { - metadata.put(EntityData.NAMETAG, ""); - } else { - metadata.put(EntityData.NAMETAG, team.getPrefix() + MessageUtils.toChatColor(team.getColor()) + username + team.getSuffix()); + String displayName = ""; + if (team.isVisibleFor(session.getPlayerEntity().getUsername())) { + displayName = MessageUtils.toChatColor(team.getColor()) + username; + displayName = team.getCurrentData().getDisplayName(displayName); } + metadata.put(EntityData.NAMETAG, displayName); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaDisplayScoreboardTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaDisplayScoreboardTranslator.java index 3ee174d7..b42ac78e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaDisplayScoreboardTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaDisplayScoreboardTranslator.java @@ -36,8 +36,7 @@ public class JavaDisplayScoreboardTranslator extends PacketTranslator cachedData.updateTime || + (currentData.team != null && currentData.team.shouldUpdate()); + } + + public void update(String objectiveName) { + if (cachedData == null) { + cachedData = new ScoreData(); + cachedData.updateType = UpdateType.ADD; + if (currentData.updateType == UpdateType.REMOVE) { + cachedData.updateType = UpdateType.REMOVE; + } + } else { + cachedData.updateType = currentData.updateType; + } + + cachedData.updateTime = currentData.updateTime; + cachedData.team = currentData.team; + cachedData.score = currentData.score; + + String name = this.name; + if (cachedData.team != null) { + cachedData.team.prepareUpdate(); + name = cachedData.team.getDisplayName(name); + } + cachedInfo = new ScoreInfo(id, objectiveName, cachedData.score, name); + } + + @Getter + public static final class ScoreData { + protected UpdateType updateType; + protected long updateTime; + + private Team team; + private int score; + + protected ScoreData() { + updateType = UpdateType.ADD; + } } } diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java b/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java index 732a056e..8eaa2e27 100644 --- a/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java +++ b/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java @@ -43,7 +43,7 @@ import java.util.concurrent.atomic.AtomicLong; import static org.geysermc.connector.scoreboard.UpdateType.*; @Getter -public class Scoreboard { +public final class Scoreboard { private final GeyserSession session; private final GeyserLogger logger; private final AtomicLong nextId = new AtomicLong(0); @@ -51,7 +51,8 @@ public class Scoreboard { private final Map objectives = new ConcurrentHashMap<>(); private final Map teams = new HashMap<>(); - private int lastScoreCount = 0; + private int lastAddScoreCount = 0; + private int lastRemoveScoreCount = 0; public Scoreboard(GeyserSession session) { this.session = session; @@ -59,19 +60,21 @@ public class Scoreboard { } public Objective registerNewObjective(String objectiveId, boolean active) { - if (active || objectives.containsKey(objectiveId)) { - return objectives.get(objectiveId); + Objective objective = objectives.get(objectiveId); + if (active || objective != null) { + return objective; } - Objective objective = new Objective(this, objectiveId); + objective = new Objective(this, objectiveId); objectives.put(objectiveId, objective); return objective; } - public Objective registerNewObjective(String objectiveId, ScoreboardPosition displaySlot) { + public Objective displayObjective(String objectiveId, ScoreboardPosition displaySlot) { Objective objective = objectives.get(objectiveId); if (objective != null) { if (!objective.isActive()) { objective.setActive(displaySlot); + removeOldObjectives(objective); return objective; } despawnObjective(objective); @@ -79,9 +82,21 @@ public class Scoreboard { objective = new Objective(this, objectiveId, displaySlot, "unknown", 0); objectives.put(objectiveId, objective); + removeOldObjectives(objective); return objective; } + private void removeOldObjectives(Objective newObjective) { + for (Objective next : objectives.values()) { + if (next.getId() == newObjective.getId()) { + continue; + } + if (next.getDisplaySlot() == newObjective.getDisplaySlot()) { + next.setUpdateType(REMOVE); + } + } + } + public Team registerNewTeam(String teamName, Set players) { Team team = teams.get(teamName); if (team != null) { @@ -89,7 +104,7 @@ public class Scoreboard { return team; } - team = new Team(this, teamName).setEntities(players); + team = new Team(this, teamName).addEntities(players); teams.put(teamName, team); return team; } @@ -117,8 +132,9 @@ public class Scoreboard { } public void onUpdate() { - List addScores = new ArrayList<>(getLastScoreCount()); - List removeScores = new ArrayList<>(getLastScoreCount()); + List addScores = new ArrayList<>(getLastAddScoreCount()); + List removeScores = new ArrayList<>(getLastRemoveScoreCount()); + List removedObjectives = new ArrayList<>(); for (Objective objective : objectives.values()) { if (!objective.isActive()) { @@ -129,65 +145,58 @@ public class Scoreboard { // hearts can't hold teams, so we treat them differently if (objective.getType() == 1) { for (Score score : objective.getScores().values()) { - if (score.getUpdateType() == NOTHING) { - continue; - } + boolean update = score.shouldUpdate(); - boolean update = score.getUpdateType() == UPDATE; if (update) { - score.update(); + score.update(objective.getObjectiveName()); } - if (score.getUpdateType() == ADD || update) { + if (score.getUpdateType() != REMOVE && update) { addScores.add(score.getCachedInfo()); } - if (score.getUpdateType() == REMOVE || update) { + if (score.getUpdateType() != ADD && update) { removeScores.add(score.getCachedInfo()); } } continue; } - boolean globalUpdate = objective.getUpdateType() == UPDATE; - boolean globalAdd = objective.getUpdateType() == ADD; - boolean globalRemove = objective.getUpdateType() == REMOVE; + boolean objectiveUpdate = objective.getUpdateType() == UPDATE; + boolean objectiveAdd = objective.getUpdateType() == ADD; + boolean objectiveRemove = objective.getUpdateType() == REMOVE; for (Score score : objective.getScores().values()) { Team team = score.getTeam(); - boolean add = globalAdd || globalUpdate; - boolean remove = globalRemove; - boolean teamChanged = false; + boolean add = objectiveAdd || objectiveUpdate; + boolean remove = false; if (team != null) { if (team.getUpdateType() == REMOVE || !team.hasEntity(score.getName())) { score.setTeam(null); - teamChanged = true; + add = true; + remove = true; } - - teamChanged |= team.getUpdateType() == UPDATE; - - add |= team.getUpdateType() == ADD || team.getUpdateType() == UPDATE; - remove |= team.getUpdateType() != NOTHING; } - add |= score.getUpdateType() == ADD || score.getUpdateType() == UPDATE; - remove |= score.getUpdateType() == REMOVE || score.getUpdateType() == UPDATE; + add |= score.shouldUpdate(); + remove |= score.shouldUpdate(); - if (score.getUpdateType() == REMOVE || globalRemove) { + if (score.getUpdateType() == REMOVE || objectiveRemove) { add = false; } - if (score.getUpdateType() == ADD) { + if (score.getUpdateType() == ADD || objectiveRemove) { remove = false; } - if (score.getUpdateType() == ADD || score.getUpdateType() == UPDATE || teamChanged) { - score.update(); + if (score.shouldUpdate()) { + score.update(objective.getObjectiveName()); } if (add) { addScores.add(score.getCachedInfo()); } + if (remove) { removeScores.add(score.getCachedInfo()); } @@ -200,17 +209,17 @@ public class Scoreboard { score.setUpdateType(NOTHING); } - if (globalRemove || globalUpdate) { + if (objectiveRemove) { + removedObjectives.add(objective); + } + + if (objectiveUpdate) { RemoveObjectivePacket removeObjectivePacket = new RemoveObjectivePacket(); removeObjectivePacket.setObjectiveId(objective.getObjectiveName()); session.sendUpstreamPacket(removeObjectivePacket); - if (globalRemove) { - objectives.remove(objective.getObjectiveName()); // now we can deregister - objective.removed(); - } } - if ((globalAdd || globalUpdate) && !globalRemove) { + if ((objectiveAdd || objectiveUpdate) && !objectiveRemove) { SetDisplayObjectivePacket displayObjectivePacket = new SetDisplayObjectivePacket(); displayObjectivePacket.setObjectiveId(objective.getObjectiveName()); displayObjectivePacket.setDisplayName(objective.getDisplayName()); @@ -223,6 +232,20 @@ public class Scoreboard { objective.setUpdateType(NOTHING); } + Iterator teamIterator = teams.values().iterator(); + while (teamIterator.hasNext()) { + Team current = teamIterator.next(); + + switch (current.getUpdateType()) { + case ADD: + case UPDATE: + current.markUpdated(); + break; + case REMOVE: + teamIterator.remove(); + } + } + if (!removeScores.isEmpty()) { SetScorePacket setScorePacket = new SetScorePacket(); setScorePacket.setAction(SetScorePacket.Action.REMOVE); @@ -237,37 +260,27 @@ public class Scoreboard { session.sendUpstreamPacket(setScorePacket); } - lastScoreCount = addScores.size(); + // prevents crashes in some cases + for (Objective objective : removedObjectives) { + despawnObjective(objective); + } + + lastAddScoreCount = addScores.size(); + lastRemoveScoreCount = removeScores.size(); } public void despawnObjective(Objective objective) { + objectives.remove(objective.getObjectiveName()); + objective.removed(); + RemoveObjectivePacket removeObjectivePacket = new RemoveObjectivePacket(); removeObjectivePacket.setObjectiveId(objective.getObjectiveName()); session.sendUpstreamPacket(removeObjectivePacket); - objectives.remove(objective.getDisplayName()); - - List toRemove = new ArrayList<>(); - for (String identifier : objective.getScores().keySet()) { - Score score = objective.getScores().get(identifier); - toRemove.add(new ScoreInfo( - score.getId(), score.getObjective().getObjectiveName(), - 0, "" - )); - } - - objective.removed(); - - if (!toRemove.isEmpty()) { - SetScorePacket setScorePacket = new SetScorePacket(); - setScorePacket.setAction(SetScorePacket.Action.REMOVE); - setScorePacket.setInfos(toRemove); - session.sendUpstreamPacket(setScorePacket); - } } public Team getTeamFor(String entity) { for (Team team : teams.values()) { - if (team.getEntities().contains(entity)) { + if (team.hasEntity(entity)) { return team; } } diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java b/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java index a073e2e9..377a15f1 100644 --- a/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java +++ b/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java @@ -28,6 +28,7 @@ package org.geysermc.connector.scoreboard; import com.github.steveice10.mc.protocol.data.game.scoreboard.NameTagVisibility; import com.github.steveice10.mc.protocol.data.game.scoreboard.TeamColor; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; @@ -36,62 +37,90 @@ import java.util.ArrayList; import java.util.List; import java.util.Set; -@Getter @Setter +@Getter @Accessors(chain = true) -public class Team { +public final class Team { private final Scoreboard scoreboard; private final String id; - private UpdateType updateType = UpdateType.ADD; - private String name; + @Getter(AccessLevel.NONE) + private final Set entities; + @Setter private NameTagVisibility nameTagVisibility; + @Setter private TeamColor color; - private NameTagVisibility nameTagVisibility; - private String prefix; - private TeamColor color; - private String suffix; - private Set entities = new ObjectOpenHashSet<>(); + private TeamData currentData; + private TeamData cachedData; + + private boolean updating; public Team(Scoreboard scoreboard, String id) { this.scoreboard = scoreboard; this.id = id; + currentData = new TeamData(); + entities = new ObjectOpenHashSet<>(); } - public void addEntities(String... names) { - List added = new ArrayList<>(); - for (String name : names) { - if (entities.add(name)) { - added.add(name); - } + private void checkAddedEntities(List added) { + if (added.size() == 0) { + return; } - setUpdateType(UpdateType.UPDATE); + // we don't have to change the updateType, + // because the scores itself need updating, not the team for (Objective objective : scoreboard.getObjectives().values()) { - for (Score score : objective.getScores().values()) { - if (added.contains(score.getName())) { + for (String addedEntity : added) { + Score score = objective.getScores().get(addedEntity); + if (score != null) { score.setTeam(this); } } } } + public Team addEntities(String... names) { + List added = new ArrayList<>(); + for (String name : names) { + if (entities.add(name)) { + added.add(name); + } + } + checkAddedEntities(added); + return this; + } + + public Team addEntities(Set names) { + List added = new ArrayList<>(); + for (String name : names) { + if (entities.add(name)) { + added.add(name); + } + } + checkAddedEntities(added); + return this; + } + public void removeEntities(String... names) { for (String name : names) { entities.remove(name); } - setUpdateType(UpdateType.UPDATE); } public boolean hasEntity(String name) { return entities.contains(name); } + public Team setName(String name) { + currentData.name = name; + return this; + } + public Team setPrefix(String prefix) { // replace "null" to an empty string, // we do this here to improve the performance of Score#getDisplayName if (prefix.length() == 4 && "null".equals(prefix)) { - this.prefix = ""; + currentData.prefix = ""; return this; } - this.prefix = prefix; + currentData.prefix = prefix; return this; } @@ -99,15 +128,92 @@ public class Team { // replace "null" to an empty string, // we do this here to improve the performance of Score#getDisplayName if (suffix.length() == 4 && "null".equals(suffix)) { - this.suffix = ""; + currentData.suffix = ""; return this; } - this.suffix = suffix; + currentData.suffix = suffix; return this; } + public String getDisplayName(String score) { + return cachedData != null ? + cachedData.getDisplayName(score) : + currentData.getDisplayName(score); + } + + public void markUpdated() { + updating = false; + } + + public boolean shouldUpdate() { + return updating || cachedData == null || currentData.updateTime > cachedData.updateTime; + } + + public void prepareUpdate() { + if (updating) { + return; + } + updating = true; + + if (cachedData == null) { + cachedData = new TeamData(); + cachedData.updateType = currentData.updateType != UpdateType.REMOVE ? UpdateType.ADD : UpdateType.REMOVE; + } else { + cachedData.updateType = currentData.updateType; + } + + cachedData.updateTime = currentData.updateTime; + cachedData.name = currentData.name; + cachedData.prefix = currentData.prefix; + cachedData.suffix = currentData.suffix; + } + + public UpdateType getUpdateType() { + return cachedData != null ? cachedData.updateType : currentData.updateType; + } + + public Team setUpdateType(UpdateType updateType) { + if (updateType != UpdateType.NOTHING) { + currentData.updateTime = System.currentTimeMillis(); + } + currentData.updateType = updateType; + return this; + } + + public boolean isVisibleFor(String entity) { + switch (nameTagVisibility) { + case HIDE_FOR_OTHER_TEAMS: + return hasEntity(entity); + case HIDE_FOR_OWN_TEAM: + return !hasEntity(entity); + case ALWAYS: + return true; + case NEVER: + return false; + } + return true; + } + @Override public int hashCode() { return id.hashCode(); } + + @Getter + public static final class TeamData { + protected UpdateType updateType; + protected long updateTime; + + protected String name; + protected String prefix; + protected String suffix; + + protected TeamData() { + updateType = UpdateType.ADD; + } + + public String getDisplayName(String score) { + return prefix + score + suffix; + } + } } From 109922f796c9f7c0f136e8a536357215a7afcf0c Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Mon, 9 Nov 2020 14:26:29 -0500 Subject: [PATCH 032/116] Update MCProtocolLib to fix datapacks that send empty result recipes (#1522) Example: https://www.planetminecraft.com/data-pack/true-survival-a-hardcore-minecraft-experience/ --- connector/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/connector/pom.xml b/connector/pom.xml index edf14abb..ee3164e1 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -109,9 +109,9 @@ compile
- com.github.GeyserMC + com.github.steveice10 mcprotocollib - e4181064d1 + 6ac79c14d6 compile From 4237503d6d0ae7c942f74f505f7706eba9996626 Mon Sep 17 00:00:00 2001 From: David Choo Date: Tue, 10 Nov 2020 12:05:16 -0500 Subject: [PATCH 033/116] Fix laying crash (#1510) * Fix crash with GSit lay and use Java bed position data * Fix GSit's lay position * Move Bed Position metadata to the right class * Actually fix lay for PosePlugin * Revert "Actually fix lay for PosePlugin" This reverts commit 3f21261162c439b3b167f057e75c2884193b75f7. Co-authored-by: DoctorMacc --- .../org/geysermc/connector/entity/Entity.java | 12 ---------- .../connector/entity/LivingEntity.java | 14 ++++++++++++ .../connector/entity/PlayerEntity.java | 13 +++++++++++ .../living/merchant/VillagerEntity.java | 7 +++--- .../spawn/JavaSpawnPlayerTranslator.java | 22 ++++++++++++------- 5 files changed, 45 insertions(+), 23 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/entity/Entity.java b/connector/src/main/java/org/geysermc/connector/entity/Entity.java index 2dfb0c04..e579f4e9 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/Entity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/Entity.java @@ -339,18 +339,6 @@ public class Entity { metadata.getFlags().setFlag(EntityFlag.SLEEPING, true); // Has to be a byte or it does not work metadata.put(EntityData.PLAYER_FLAGS, (byte) 2); - if (entityId == session.getPlayerEntity().getEntityId()) { - Vector3i lastInteractionPos = session.getLastInteractionPosition(); - metadata.put(EntityData.BED_POSITION, lastInteractionPos); - if (session.getConnector().getConfig().isCacheChunks()) { - int bed = session.getConnector().getWorldManager().getBlockAt(session, lastInteractionPos.getX(), - lastInteractionPos.getY(), lastInteractionPos.getZ()); - // Bed has to be updated, or else player is floating in the air - ChunkUtils.updateBlock(session, bed, lastInteractionPos); - } - } else { - metadata.put(EntityData.BED_POSITION, Vector3i.from(position.getFloorX(), position.getFloorY() - 2, position.getFloorZ())); - } metadata.put(EntityData.BOUNDING_BOX_WIDTH, 0.2f); metadata.put(EntityData.BOUNDING_BOX_HEIGHT, 0.2f); } else if (metadata.getFlags().getFlag(EntityFlag.SLEEPING)) { diff --git a/connector/src/main/java/org/geysermc/connector/entity/LivingEntity.java b/connector/src/main/java/org/geysermc/connector/entity/LivingEntity.java index ae9eaee5..345c19de 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/LivingEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/LivingEntity.java @@ -26,7 +26,9 @@ package org.geysermc.connector.entity; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; 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.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; @@ -42,6 +44,7 @@ import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.utils.AttributeUtils; +import org.geysermc.connector.utils.ChunkUtils; import java.util.ArrayList; import java.util.List; @@ -84,6 +87,17 @@ public class LivingEntity extends Entity { case 10: metadata.put(EntityData.EFFECT_AMBIENT, (byte) ((boolean) entityMetadata.getValue() ? 1 : 0)); break; + case 13: // Bed Position + Position bedPosition = (Position) entityMetadata.getValue(); + if (bedPosition != null) { + metadata.put(EntityData.BED_POSITION, Vector3i.from(bedPosition.getX(), bedPosition.getY(), bedPosition.getZ())); + if (session.getConnector().getConfig().isCacheChunks()) { + int bed = session.getConnector().getWorldManager().getBlockAt(session, bedPosition); + // Bed has to be updated, or else player is floating in the air + ChunkUtils.updateBlock(session, bed, bedPosition); + } + } + break; } super.updateBedrockMetadata(entityMetadata, session); diff --git a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java index bfadd83c..8eeae473 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java @@ -30,10 +30,12 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadat import com.github.steveice10.mc.protocol.data.message.TextMessage; 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.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.data.entity.EntityLinkData; import com.nukkitx.protocol.bedrock.packet.AddPlayerPacket; import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket; @@ -167,6 +169,17 @@ public class PlayerEntity extends LivingEntity { movePlayerPacket.setRotation(getBedrockRotation()); movePlayerPacket.setOnGround(isOnGround); movePlayerPacket.setMode(MovePlayerPacket.Mode.NORMAL); + // If the player is moved while sleeping, we have to adjust their y, so it appears + // correctly on Bedrock. This fixes GSit's lay. + if (metadata.getFlags().getFlag(EntityFlag.SLEEPING)) { + Vector3i bedPosition = metadata.getPos(EntityData.BED_POSITION); + if (bedPosition != null && (bedPosition.getY() == 0 || bedPosition.distanceSquared(position.toInt()) > 4)) { + // Force the player movement by using a teleport + movePlayerPacket.setPosition(Vector3f.from(position.getX(), position.getY() - entityType.getOffset() + 0.2f, position.getZ())); + movePlayerPacket.setMode(MovePlayerPacket.Mode.TELEPORT); + movePlayerPacket.setTeleportationCause(MovePlayerPacket.TeleportationCause.UNKNOWN); + } + } session.sendUpstreamPacket(movePlayerPacket); if (leftParrot != null) { leftParrot.moveRelative(session, relX, relY, relZ, rotation, true); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java index 7ada302c..98d5a631 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.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.VillagerData; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; 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.entity.EntityFlag; import com.nukkitx.protocol.bedrock.data.entity.EntityFlags; @@ -99,9 +100,9 @@ public class VillagerEntity extends AbstractMerchantEntity { int bedId = 0; float bedPositionSubtractorW = 0; float bedPositionSubtractorN = 0; - if (session.getConnector().getConfig().isCacheChunks()) { - Position bedLocation = new Position((int) position.getFloorX(), (int) position.getFloorY(), (int) position.getFloorZ()); - bedId = session.getConnector().getWorldManager().getBlockAt(session, bedLocation); + Vector3i bedPosition = metadata.getPos(EntityData.BED_POSITION); + if (session.getConnector().getConfig().isCacheChunks() && bedPosition != null) { + bedId = session.getConnector().getWorldManager().getBlockAt(session, bedPosition); } String bedRotationZ = BlockTranslator.getJavaIdBlockMap().inverse().get(bedId); setRotation(rotation); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnPlayerTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnPlayerTranslator.java index 47beca8a..99a6b049 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnPlayerTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnPlayerTranslator.java @@ -43,15 +43,21 @@ public class JavaSpawnPlayerTranslator extends PacketTranslator Date: Wed, 11 Nov 2020 18:13:13 +0000 Subject: [PATCH 034/116] Fix even more entity metadata flags (#1483) * Fix even more entity metadata flags * Add comment explaining magic value * Fix horse flags and add more information * Add more information about the Horse eating particles --- .../connector/entity/living/BatEntity.java | 48 ++++++++++++++++++ .../entity/living/SnowGolemEntity.java | 49 ++++++++++++++++++ .../entity/living/animal/FoxEntity.java | 1 + .../entity/living/animal/MooshroomEntity.java | 47 +++++++++++++++++ .../entity/living/animal/RabbitEntity.java | 9 ++++ .../entity/living/animal/TurtleEntity.java | 49 ++++++++++++++++++ .../animal/horse/AbstractHorseEntity.java | 28 +++++++++++ .../entity/living/monster/VexEntity.java | 50 +++++++++++++++++++ .../connector/entity/type/EntityType.java | 10 ++-- .../translators/item/ItemRegistry.java | 7 +++ 10 files changed, 293 insertions(+), 5 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/entity/living/BatEntity.java create mode 100644 connector/src/main/java/org/geysermc/connector/entity/living/SnowGolemEntity.java create mode 100644 connector/src/main/java/org/geysermc/connector/entity/living/animal/MooshroomEntity.java create mode 100644 connector/src/main/java/org/geysermc/connector/entity/living/animal/TurtleEntity.java create mode 100644 connector/src/main/java/org/geysermc/connector/entity/living/monster/VexEntity.java diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/BatEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/BatEntity.java new file mode 100644 index 00000000..b7b7534c --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/BatEntity.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2019-2020 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.connector.entity.living; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class BatEntity extends AmbientEntity { + + public BatEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 15) { + byte xd = (byte) entityMetadata.getValue(); + metadata.getFlags().setFlag(EntityFlag.RESTING, (xd & 0x01) == 0x01); + } + super.updateBedrockMetadata(entityMetadata, session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/SnowGolemEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/SnowGolemEntity.java new file mode 100644 index 00000000..2f75e645 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/SnowGolemEntity.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2019-2020 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.connector.entity.living; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class SnowGolemEntity extends GolemEntity { + + public SnowGolemEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 15) { + byte xd = (byte) entityMetadata.getValue(); + // Handle the visibility of the pumpkin + metadata.getFlags().setFlag(EntityFlag.SHEARED, (xd & 0x10) != 0x10); + } + super.updateBedrockMetadata(entityMetadata, session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/FoxEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/FoxEntity.java index 88c30cbf..1d924994 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/FoxEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/FoxEntity.java @@ -47,6 +47,7 @@ public class FoxEntity extends AnimalEntity { byte xd = (byte) entityMetadata.getValue(); metadata.getFlags().setFlag(EntityFlag.SITTING, (xd & 0x01) == 0x01); metadata.getFlags().setFlag(EntityFlag.SNEAKING, (xd & 0x04) == 0x04); + metadata.getFlags().setFlag(EntityFlag.INTERESTED, (xd & 0x08) == 0x08); metadata.getFlags().setFlag(EntityFlag.SLEEPING, (xd & 0x20) == 0x20); } super.updateBedrockMetadata(entityMetadata, session); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/MooshroomEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/MooshroomEntity.java new file mode 100644 index 00000000..69fb55fb --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/MooshroomEntity.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019-2020 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.connector.entity.living.animal; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class MooshroomEntity extends AnimalEntity { + + public MooshroomEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 16) { + metadata.put(EntityData.VARIANT, entityMetadata.getValue().equals("brown") ? 1 : 0); + } + super.updateBedrockMetadata(entityMetadata, session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/RabbitEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/RabbitEntity.java index 0b61713a..79202792 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/RabbitEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/RabbitEntity.java @@ -48,6 +48,15 @@ public class RabbitEntity extends AnimalEntity { metadata.put(EntityData.SCALE, .35f); metadata.getFlags().setFlag(EntityFlag.BABY, true); } + } else if (entityMetadata.getId() == 16) { + int variant = (int) entityMetadata.getValue(); + + // Change the killer bunny to display as white since it only exists on Java Edition + if (variant == 99) { + variant = 1; + } + + metadata.put(EntityData.VARIANT, variant); } } } \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/TurtleEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/TurtleEntity.java new file mode 100644 index 00000000..555e2268 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/TurtleEntity.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2019-2020 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.connector.entity.living.animal; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class TurtleEntity extends AnimalEntity { + + public TurtleEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 17) { + metadata.getFlags().setFlag(EntityFlag.IS_PREGNANT, (boolean) entityMetadata.getValue()); + } else if (entityMetadata.getId() == 18) { + metadata.getFlags().setFlag(EntityFlag.LAYING_EGG, (boolean) entityMetadata.getValue()); + } + super.updateBedrockMetadata(entityMetadata, session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/AbstractHorseEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/AbstractHorseEntity.java index e08f9adf..cf9f84b4 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/AbstractHorseEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/AbstractHorseEntity.java @@ -27,10 +27,14 @@ package org.geysermc.connector.entity.living.animal.horse; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; 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.packet.EntityEventPacket; import org.geysermc.connector.entity.living.animal.AnimalEntity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.item.ItemRegistry; public class AbstractHorseEntity extends AnimalEntity { @@ -47,6 +51,30 @@ public class AbstractHorseEntity extends AnimalEntity { metadata.getFlags().setFlag(EntityFlag.SADDLED, (xd & 0x04) == 0x04); metadata.getFlags().setFlag(EntityFlag.EATING, (xd & 0x10) == 0x10); metadata.getFlags().setFlag(EntityFlag.STANDING, (xd & 0x20) == 0x20); + + // HorseFlags + // Bred 0x10 + // Eating 0x20 + // Open mouth 0x80 + int horseFlags = 0x0; + horseFlags = (xd & 0x40) == 0x40 ? horseFlags | 0x80 : horseFlags; + + // Only set eating when we don't have mouth open so a player interaction doesn't trigger the eating animation + horseFlags = (xd & 0x10) == 0x10 && (xd & 0x40) != 0x40 ? horseFlags | 0x20 : horseFlags; + + // Set the flags into the display item + metadata.put(EntityData.DISPLAY_ITEM, horseFlags); + + // Send the eating particles + // We use the wheat metadata as static particles since Java + // doesn't send over what item was used to feed the horse + if ((xd & 0x40) == 0x40) { + EntityEventPacket entityEventPacket = new EntityEventPacket(); + entityEventPacket.setRuntimeEntityId(geyserId); + entityEventPacket.setType(EntityEventType.EATING_ITEM); + entityEventPacket.setData(ItemRegistry.WHEAT.getBedrockId() << 16); + session.sendUpstreamPacket(entityEventPacket); + } } // Needed to control horses diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/VexEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/VexEntity.java new file mode 100644 index 00000000..70e41329 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/VexEntity.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2019-2020 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.connector.entity.living.monster; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class VexEntity extends MonsterEntity { + + public VexEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 15) { + byte xd = (byte) entityMetadata.getValue(); + // Set the target to the player to force the attack animation + // even if the player isn't the target as we dont get the target on Java + metadata.put(EntityData.TARGET_EID, (xd & 0x01) == 0x01 ? session.getPlayerEntity().getGeyserId() : 0); + } + super.updateBedrockMetadata(entityMetadata, session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java index 7557bd04..f023ca10 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java +++ b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java @@ -47,12 +47,12 @@ public enum EntityType { SHEEP(SheepEntity.class, 13, 1.3f, 0.9f), WOLF(WolfEntity.class, 14, 0.85f, 0.6f), VILLAGER(VillagerEntity.class, 15, 1.8f, 0.6f, 0.6f, 1.62f, "minecraft:villager_v2"), - MOOSHROOM(AnimalEntity.class, 16, 1.4f, 0.9f), + MOOSHROOM(MooshroomEntity.class, 16, 1.4f, 0.9f), SQUID(SquidEntity.class, 17, 0.8f), RABBIT(RabbitEntity.class, 18, 0.5f, 0.4f), - BAT(AmbientEntity.class, 19, 0.9f, 0.5f), + BAT(BatEntity.class, 19, 0.9f, 0.5f), IRON_GOLEM(GolemEntity.class, 20, 2.7f, 1.4f), - SNOW_GOLEM(GolemEntity.class, 21, 1.9f, 0.7f), + SNOW_GOLEM(SnowGolemEntity.class, 21, 1.9f, 0.7f), OCELOT(OcelotEntity.class, 22, 0.35f, 0.3f), HORSE(HorseEntity.class, 23, 1.6f, 1.3965f), DONKEY(ChestedHorseEntity.class, 24, 1.6f, 1.3965f), @@ -109,7 +109,7 @@ public enum EntityType { END_CRYSTAL(EnderCrystalEntity.class, 71, 2.0f, 2.0f, 2.0f, 0f, "minecraft:ender_crystal"), FIREWORK_ROCKET(FireworkEntity.class, 72, 0.25f, 0.25f, 0.25f, 0f, "minecraft:fireworks_rocket"), TRIDENT(TridentEntity.class, 73, 0f, 0f, 0f, 0f, "minecraft:thrown_trident"), - TURTLE(AnimalEntity.class, 74, 0.4f, 1.2f), + TURTLE(TurtleEntity.class, 74, 0.4f, 1.2f), CAT(CatEntity.class, 75, 0.35f, 0.3f), SHULKER_BULLET(Entity.class, 76, 0.3125f), FISHING_BOBBER(FishingHookEntity.class, 77, 0f, 0f, 0f, 0f, "minecraft:fishing_hook"), @@ -141,7 +141,7 @@ public enum EntityType { LLAMA_SPIT(Entity.class, 102, 0.25f), EVOKER_FANGS(Entity.class, 103, 0.8f, 0.5f, 0.5f, 0f, "minecraft:evocation_fang"), EVOKER(SpellcasterIllagerEntity.class, 104, 1.95f, 0.6f, 0.6f, 0f, "minecraft:evocation_illager"), - VEX(MonsterEntity.class, 105, 0.8f, 0.4f), + VEX(VexEntity.class, 105, 0.8f, 0.4f), ICE_BOMB(Entity.class, 106, 0f), BALLOON(Entity.class, 107, 0f), //TODO PUFFERFISH(PufferFishEntity.class, 108, 0.7f, 0.7f), diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java index b52d27ff..8370ba8e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java @@ -75,6 +75,10 @@ public class ItemRegistry { * Shield item entry, used in Entity.java and LivingEntity.java */ public static ItemEntry SHIELD; + /** + * Wheat item entry, used in AbstractHorseEntity.java + */ + public static ItemEntry WHEAT; public static int BARRIER_INDEX = 0; @@ -157,6 +161,9 @@ public class ItemRegistry { case "minecraft:bucket": BUCKET = ITEM_ENTRIES.get(itemIndex); break; + case "minecraft:wheat": + WHEAT = ITEM_ENTRIES.get(itemIndex); + break; default: break; } From e748240a02c9eb7062fb114f3dcd285f4efb47fe Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Wed, 11 Nov 2020 19:28:45 -0500 Subject: [PATCH 035/116] Add more interactive tags (mobile buttons) (#1443) * Add more interactive tags (mobile buttons) This expands our support for showing the interactive tags on touchscreen and console setups. This is not complete - specifically, the food compatibility of creatures needs to be expanded upon (I will work on this later and does not stop this PR from being mergable). This also includes: - Creepers who are ignited with flint and steel now show up properly - Zombie villagers now shake properly when converting and show their region outfits * Add more food choices and add more panda entity metadata * Re-add eating flag * Remove debug line * Refactor dimension usage, finish interactive tag usage, bees * Print statements... ._. * Don't make eating item packet data a non-constant * Move BAMBOO to ItemRegistry * Add missing break * Make changes * Minor final changes --- .../standalone/GeyserStandaloneBootstrap.java | 4 +- .../geysermc/connector/GeyserConnector.java | 3 +- .../org/geysermc/connector/entity/Entity.java | 3 - .../entity/living/animal/BeeEntity.java | 18 +- .../entity/living/animal/FoxEntity.java | 2 +- .../entity/living/animal/HoglinEntity.java | 50 +++ .../entity/living/animal/PandaEntity.java | 54 ++- .../living/animal/horse/HorseEntity.java | 3 +- .../living/animal/tameable/CatEntity.java | 13 +- .../animal/tameable/TameableEntity.java | 24 +- .../living/merchant/VillagerEntity.java | 15 +- .../living/monster/BasePiglinEntity.java | 16 +- .../entity/living/monster/CreeperEntity.java | 13 +- .../entity/living/monster/PiglinEntity.java | 2 +- .../living/monster/ZombieVillagerEntity.java | 56 +++ .../connector/entity/type/EntityType.java | 4 +- .../network/session/GeyserSession.java | 9 +- .../player/BedrockInteractTranslator.java | 381 +++++++++++++++--- .../translators/item/ItemRegistry.java | 7 + .../java/JavaJoinGameTranslator.java | 4 +- .../java/JavaRespawnTranslator.java | 4 +- .../entity/JavaEntityAttachTranslator.java | 2 +- .../entity/JavaEntityStatusTranslator.java | 20 +- .../java/world/JavaMapDataTranslator.java | 2 +- .../world/JavaSpawnParticleTranslator.java | 2 +- .../world/JavaSpawnPositionTranslator.java | 2 +- .../connector/utils/DimensionUtils.java | 29 +- .../geysermc/connector/utils/LocaleUtils.java | 11 +- 28 files changed, 643 insertions(+), 110 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/entity/living/animal/HoglinEntity.java create mode 100644 connector/src/main/java/org/geysermc/connector/entity/living/monster/ZombieVillagerEntity.java diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java index f58a997e..f4dfd454 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java @@ -103,11 +103,11 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { case "--config": case "-c": if (i >= args.length - 1) { - System.err.println(MessageFormat.format(LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.confignotspecified"), "-c")); + System.err.println(MessageFormat.format(LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.config_not_specified"), "-c")); return; } configFilenameOpt = args[i+1]; i++; - System.out.println(MessageFormat.format(LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.configspecified"), configFilenameOpt)); + System.out.println(MessageFormat.format(LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.config_specified"), configFilenameOpt)); break; case "--help": case "-h": diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 1d535f54..2497166c 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -179,8 +179,7 @@ public class GeyserConnector { remoteServer = new RemoteServer(config.getRemote().getAddress(), remotePort); authType = AuthType.getByName(config.getRemote().getAuthType()); - if (config.isAboveBedrockNetherBuilding()) - DimensionUtils.changeBedrockNetherId(); // Apply End dimension ID workaround to Nether + DimensionUtils.changeBedrockNetherId(config.isAboveBedrockNetherBuilding()); // Apply End dimension ID workaround to Nether // https://github.com/GeyserMC/Geyser/issues/957 RakNetConstants.MAXIMUM_MTU_SIZE = (short) config.getMtu(); diff --git a/connector/src/main/java/org/geysermc/connector/entity/Entity.java b/connector/src/main/java/org/geysermc/connector/entity/Entity.java index e579f4e9..20cd2f76 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/Entity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/Entity.java @@ -67,8 +67,6 @@ public class Entity { protected long entityId; protected long geyserId; - protected String dimension; - protected Vector3f position; protected Vector3f motion; @@ -100,7 +98,6 @@ public class Entity { this.rotation = rotation; this.valid = false; - this.dimension = "minecraft:overworld"; setPosition(position); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/BeeEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/BeeEntity.java index c46f00fe..ee17e2a2 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/BeeEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/BeeEntity.java @@ -27,7 +27,10 @@ package org.geysermc.connector.entity.living.animal; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; 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.packet.EntityEventPacket; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; @@ -41,10 +44,23 @@ public class BeeEntity extends AnimalEntity { public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { if (entityMetadata.getId() == 16) { byte xd = (byte) entityMetadata.getValue(); - metadata.getFlags().setFlag(EntityFlag.ANGRY, (xd & 0x02) == 0x02); + // Bee is performing sting attack; trigger animation + if ((xd & 0x02) == 0x02) { + EntityEventPacket packet = new EntityEventPacket(); + packet.setRuntimeEntityId(geyserId); + packet.setType(EntityEventType.ATTACK_START); + packet.setData(0); + session.sendUpstreamPacket(packet); + } + // If the bee has stung + metadata.put(EntityData.MARK_VARIANT, (xd & 0x04) == 0x04 ? 1 : 0); // If the bee has nectar or not metadata.getFlags().setFlag(EntityFlag.POWERED, (xd & 0x08) == 0x08); } + if (entityMetadata.getId() == 17) { + // Converting "anger time" to a boolean + metadata.getFlags().setFlag(EntityFlag.ANGRY, (int) entityMetadata.getValue() > 0); + } super.updateBedrockMetadata(entityMetadata, session); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/FoxEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/FoxEntity.java index 1d924994..bbc2d7de 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/FoxEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/FoxEntity.java @@ -41,7 +41,7 @@ public class FoxEntity extends AnimalEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { if (entityMetadata.getId() == 16) { - metadata.put(EntityData.VARIANT, (int) entityMetadata.getValue()); + metadata.put(EntityData.VARIANT, entityMetadata.getValue()); } if (entityMetadata.getId() == 17) { byte xd = (byte) entityMetadata.getValue(); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/HoglinEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/HoglinEntity.java new file mode 100644 index 00000000..3fd29172 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/HoglinEntity.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2019-2020 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.connector.entity.living.animal; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.utils.DimensionUtils; + +public class HoglinEntity extends AnimalEntity { + + public HoglinEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 16) { + // Immune to zombification? + // Apply shaking effect if not in the nether and zombification is possible + metadata.getFlags().setFlag(EntityFlag.SHAKING, !((boolean) entityMetadata.getValue()) && !session.getDimension().equals(DimensionUtils.NETHER)); + } + super.updateBedrockMetadata(entityMetadata, session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/PandaEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/PandaEntity.java index 7e555122..ed3ed80b 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/PandaEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/PandaEntity.java @@ -27,23 +27,75 @@ package org.geysermc.connector.entity.living.animal; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; 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.packet.EntityEventPacket; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.item.ItemRegistry; public class PandaEntity extends AnimalEntity { + private int mainGene; + private int hiddenGene; + public PandaEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); } @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 18) { + metadata.getFlags().setFlag(EntityFlag.EATING, (int) entityMetadata.getValue() > 0); + metadata.put(EntityData.EATING_COUNTER, entityMetadata.getValue()); + if ((int) entityMetadata.getValue() != 0) { + // Particles and sound + EntityEventPacket packet = new EntityEventPacket(); + packet.setRuntimeEntityId(geyserId); + packet.setType(EntityEventType.EATING_ITEM); + packet.setData(ItemRegistry.BAMBOO.getBedrockId() << 16); + session.sendUpstreamPacket(packet); + } + } + if (entityMetadata.getId() == 19) { + mainGene = (int) (byte) entityMetadata.getValue(); + updateAppearance(); + } + if (entityMetadata.getId() == 20) { + hiddenGene = (int) (byte) entityMetadata.getValue(); + updateAppearance(); + } if (entityMetadata.getId() == 21) { byte xd = (byte) entityMetadata.getValue(); metadata.getFlags().setFlag(EntityFlag.SNEEZING, (xd & 0x02) == 0x02); - metadata.getFlags().setFlag(EntityFlag.EATING, (xd & 0x04) == 0x04); + metadata.getFlags().setFlag(EntityFlag.ROLLING, (xd & 0x04) == 0x04); + metadata.getFlags().setFlag(EntityFlag.SITTING, (xd & 0x08) == 0x08); + // Required to put these both for sitting to actually show + metadata.put(EntityData.SITTING_AMOUNT, (xd & 0x08) == 0x08 ? 1f : 0f); + metadata.put(EntityData.SITTING_AMOUNT_PREVIOUS, (xd & 0x08) == 0x08 ? 1f : 0f); + metadata.getFlags().setFlag(EntityFlag.LAYING_DOWN, (xd & 0x10) == 0x10); } super.updateBedrockMetadata(entityMetadata, session); } + + /** + * 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 == hiddenGene) { + // Main and hidden genes match; this is what the panda looks like. + metadata.put(EntityData.VARIANT, mainGene); + } else { + // Genes have no effect on appearance + metadata.put(EntityData.VARIANT, 0); + } + } else { + // No need to worry about hidden gene + metadata.put(EntityData.VARIANT, mainGene); + } + } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/HorseEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/HorseEntity.java index da3ff349..349da5e0 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/HorseEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/HorseEntity.java @@ -28,7 +28,6 @@ package org.geysermc.connector.entity.living.animal.horse; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; 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.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; @@ -41,7 +40,7 @@ public class HorseEntity extends AbstractHorseEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { if (entityMetadata.getId() == 18) { - metadata.put(EntityData.VARIANT, (int) entityMetadata.getValue()); + metadata.put(EntityData.VARIANT, entityMetadata.getValue()); metadata.put(EntityData.MARK_VARIANT, (((int) entityMetadata.getValue()) >> 8) % 5); } super.updateBedrockMetadata(entityMetadata, session); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/CatEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/CatEntity.java index 067a360c..5c5de546 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/CatEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/CatEntity.java @@ -34,6 +34,8 @@ import org.geysermc.connector.network.session.GeyserSession; public class CatEntity extends TameableEntity { + private byte collarColor; + public CatEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); } @@ -45,6 +47,13 @@ public class CatEntity extends TameableEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + super.updateBedrockMetadata(entityMetadata, session); + if (entityMetadata.getId() == 16) { + // Update collar color if tamed + if (metadata.getFlags().getFlag(EntityFlag.TAMED)) { + metadata.put(EntityData.COLOR, collarColor); + } + } if (entityMetadata.getId() == 18) { // Different colors in Java and Bedrock for some reason int variantColor; @@ -67,11 +76,11 @@ public class CatEntity extends TameableEntity { metadata.put(EntityData.VARIANT, variantColor); } if (entityMetadata.getId() == 21) { + collarColor = (byte) (int) entityMetadata.getValue(); // Needed or else wild cats are a red color if (metadata.getFlags().getFlag(EntityFlag.TAMED)) { - metadata.put(EntityData.COLOR, (byte) (int) entityMetadata.getValue()); + metadata.put(EntityData.COLOR, collarColor); } } - super.updateBedrockMetadata(entityMetadata, session); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/TameableEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/TameableEntity.java index 2e8ab816..9e73ebe5 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/TameableEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/TameableEntity.java @@ -29,10 +29,13 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadat 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.connector.entity.Entity; import org.geysermc.connector.entity.living.animal.AnimalEntity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; +import java.util.UUID; + public class TameableEntity extends AnimalEntity { public TameableEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { @@ -46,11 +49,22 @@ public class TameableEntity extends AnimalEntity { metadata.getFlags().setFlag(EntityFlag.SITTING, (xd & 0x01) == 0x01); metadata.getFlags().setFlag(EntityFlag.ANGRY, (xd & 0x02) == 0x02); metadata.getFlags().setFlag(EntityFlag.TAMED, (xd & 0x04) == 0x04); - // Must be set for wolf collar color to work - // Extending it to all entities to prevent future bugs - if (metadata.getFlags().getFlag(EntityFlag.TAMED)) { - metadata.put(EntityData.OWNER_EID, session.getPlayerEntity().getGeyserId()); - } // Can't de-tame an entity so no resetting the owner ID + } + + // Note: Must be set for wolf collar color to work + if (entityMetadata.getId() == 17) { + if (entityMetadata.getValue() != null) { + // Owner UUID of entity + Entity entity = session.getEntityCache().getPlayerEntity((UUID) entityMetadata.getValue()); + // Used as both a check since the player isn't in the entity cache and a normal fallback + if (entity == null) { + entity = session.getPlayerEntity(); + } + // Translate to entity ID + metadata.put(EntityData.OWNER_EID, entity.getGeyserId()); + } else { + metadata.put(EntityData.OWNER_EID, 0L); // Reset + } } super.updateBedrockMetadata(entityMetadata, session); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java index 98d5a631..028d1831 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java @@ -26,27 +26,32 @@ package org.geysermc.connector.entity.living.merchant; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.VillagerData; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.VillagerData; 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.entity.EntityFlag; -import com.nukkitx.protocol.bedrock.data.entity.EntityFlags; -import com.nukkitx.protocol.bedrock.packet.*; +import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket; import it.unimi.dsi.fastutil.ints.Int2IntMap; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.world.WorldManager; import org.geysermc.connector.network.translators.world.block.BlockTranslator; + import java.util.regex.Matcher; import java.util.regex.Pattern; public class VillagerEntity extends AbstractMerchantEntity { + /** + * A map of Java profession IDs to Bedrock IDs + */ private static final Int2IntMap VILLAGER_VARIANTS = new Int2IntOpenHashMap(); - private static final Int2IntMap VILLAGER_REGIONS = new Int2IntOpenHashMap(); + /** + * A map of all Java region IDs (plains, savanna...) to Bedrock + */ + public static final Int2IntMap VILLAGER_REGIONS = new Int2IntOpenHashMap(); static { // Java villager profession IDs -> Bedrock diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/BasePiglinEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/BasePiglinEntity.java index 830c7ea3..b83a2ca7 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/BasePiglinEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/BasePiglinEntity.java @@ -1,11 +1,25 @@ package org.geysermc.connector.entity.living.monster; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.utils.DimensionUtils; public class BasePiglinEntity extends MonsterEntity { public BasePiglinEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); } -} \ No newline at end of file + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 15) { + // Immune to zombification? + // Apply shaking effect if not in the nether and zombification is possible + metadata.getFlags().setFlag(EntityFlag.SHAKING, !((boolean) entityMetadata.getValue()) && !session.getDimension().equals(DimensionUtils.NETHER)); + } + super.updateBedrockMetadata(entityMetadata, session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/CreeperEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/CreeperEntity.java index 3c3a76bd..f4931861 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/CreeperEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/CreeperEntity.java @@ -33,6 +33,12 @@ import org.geysermc.connector.network.session.GeyserSession; public class CreeperEntity extends MonsterEntity { + /** + * Whether the creeper has been ignited and is using ID 17. + * In this instance we ignore ID 15 since it's sending us -1 which confuses poor Bedrock. + */ + private boolean ignitedByFlintAndSteel = false; + public CreeperEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); } @@ -40,13 +46,16 @@ public class CreeperEntity extends MonsterEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { if (entityMetadata.getId() == 15) { - metadata.getFlags().setFlag(EntityFlag.IGNITED, (int) entityMetadata.getValue() == 1); + if (!ignitedByFlintAndSteel) { + metadata.getFlags().setFlag(EntityFlag.IGNITED, (int) entityMetadata.getValue() == 1); + } } if (entityMetadata.getId() == 16) { metadata.getFlags().setFlag(EntityFlag.POWERED, (boolean) entityMetadata.getValue()); } if (entityMetadata.getId() == 17) { - metadata.getFlags().setFlag(EntityFlag.IGNITED, (boolean) entityMetadata.getValue()); + ignitedByFlintAndSteel = (boolean) entityMetadata.getValue(); + metadata.getFlags().setFlag(EntityFlag.IGNITED, ignitedByFlintAndSteel); } super.updateBedrockMetadata(entityMetadata, session); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/PiglinEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/PiglinEntity.java index 7b0d71e1..e0b443d3 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/PiglinEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/PiglinEntity.java @@ -41,7 +41,7 @@ public class PiglinEntity extends BasePiglinEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 15) { + if (entityMetadata.getId() == 16) { boolean isBaby = (boolean) entityMetadata.getValue(); if (isBaby) { metadata.put(EntityData.SCALE, .55f); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/ZombieVillagerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/ZombieVillagerEntity.java new file mode 100644 index 00000000..b8a62817 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/ZombieVillagerEntity.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2019-2020 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.connector.entity.living.monster; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.VillagerData; +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.connector.entity.living.merchant.VillagerEntity; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class ZombieVillagerEntity extends ZombieEntity { + + public ZombieVillagerEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 18) { + metadata.getFlags().setFlag(EntityFlag.IS_TRANSFORMING, (boolean) entityMetadata.getValue()); + metadata.getFlags().setFlag(EntityFlag.SHAKING, (boolean) entityMetadata.getValue()); + } + if (entityMetadata.getId() == 19) { + VillagerData villagerData = (VillagerData) entityMetadata.getValue(); + // Region - only one used on Bedrock + metadata.put(EntityData.MARK_VARIANT, VillagerEntity.VILLAGER_REGIONS.get(villagerData.getType())); + } + super.updateBedrockMetadata(entityMetadata, session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java index f023ca10..05447760 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java +++ b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java @@ -77,7 +77,7 @@ public enum EntityType { GHAST(GhastEntity.class, 41, 4.0f), MAGMA_CUBE(MagmaCubeEntity.class, 42, 0.51f), BLAZE(BlazeEntity.class, 43, 1.8f, 0.6f), - ZOMBIE_VILLAGER(ZombieEntity.class, 44, 1.8f, 0.6f, 0.6f, 1.62f), + ZOMBIE_VILLAGER(ZombieVillagerEntity.class, 44, 1.8f, 0.6f, 0.6f, 1.62f, "minecraft:zombie_villager_v2"), WITCH(RaidParticipantEntity.class, 45, 1.8f, 0.6f, 0.6f, 1.62f), STRAY(AbstractSkeletonEntity.class, 46, 1.8f, 0.6f, 0.6f, 1.62f), HUSK(ZombieEntity.class, 47, 1.8f, 0.6f, 0.6f, 1.62f), @@ -153,7 +153,7 @@ public enum EntityType { FOX(FoxEntity.class, 121, 0.5f, 1.25f), BEE(BeeEntity.class, 122, 0.6f, 0.6f), STRIDER(StriderEntity.class, 125, 1.7f, 0.9f, 0f, 0f, "minecraft:strider"), - HOGLIN(AnimalEntity.class, 124, 1.4f, 1.3965f, 1.3965f, 0f, "minecraft:hoglin"), + HOGLIN(HoglinEntity.class, 124, 1.4f, 1.3965f, 1.3965f, 0f, "minecraft:hoglin"), ZOGLIN(ZoglinEntity.class, 126, 1.4f, 1.3965f, 1.3965f, 0f, "minecraft:zoglin"), PIGLIN(PiglinEntity.class, 123, 1.95f, 0.6f, 0.6f, 0f, "minecraft:piglin"), PIGLIN_BRUTE(BasePiglinEntity.class, 127, 1.95f, 0.6f, 0.6f, 0f, "minecraft:piglin_brute"), diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 4429268a..a6085e21 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -146,6 +146,13 @@ public class GeyserSession implements CommandSender { @Setter private boolean jumping; + /** + * The dimension of the player. + * As all entities are in the same world, this can be safely applied to all other entities. + */ + @Setter + private String dimension = DimensionUtils.OVERWORLD; + @Setter private int breakingBlock; @@ -629,7 +636,7 @@ public class GeyserSession implements CommandSender { startGamePacket.setRotation(Vector2f.from(1, 1)); startGamePacket.setSeed(-1); - startGamePacket.setDimensionId(DimensionUtils.javaToBedrock(playerEntity.getDimension())); + startGamePacket.setDimensionId(DimensionUtils.javaToBedrock(dimension)); startGamePacket.setGeneratorId(1); startGamePacket.setLevelGameType(GameType.SURVIVAL); startGamePacket.setDifficulty(1); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockInteractTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockInteractTranslator.java index c5d6f2dd..6c03cd03 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockInteractTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockInteractTranslator.java @@ -25,27 +25,65 @@ package org.geysermc.connector.network.translators.bedrock.entity.player; -import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import com.nukkitx.protocol.bedrock.data.entity.EntityDataMap; -import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; -import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket; -import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; - 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.PlayerState; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerInteractEntityPacket; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerStatePacket; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityDataMap; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket; import com.nukkitx.protocol.bedrock.packet.InteractPacket; +import lombok.Getter; +import org.geysermc.connector.entity.Entity; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; +import org.geysermc.connector.network.translators.item.ItemEntry; import org.geysermc.connector.network.translators.item.ItemRegistry; +import java.util.Arrays; +import java.util.List; + @Translator(packet = InteractPacket.class) public class BedrockInteractTranslator extends PacketTranslator { + /** + * A list of all foods a horse/donkey can eat on Java Edition. + * Used to display interactive tag if needed. + */ + private static final List DONKEY_AND_HORSE_FOODS = Arrays.asList("golden_apple", "enchanted_golden_apple", + "golden_carrot", "sugar", "apple", "wheat", "hay_block"); + + /** + * A list of all flowers. Used for feeding bees. + */ + private static final List FLOWERS = Arrays.asList("dandelion", "poppy", "blue_orchid", "allium", "azure_bluet", + "red_tulip", "pink_tulip", "white_tulip", "orange_tulip", "cornflower", "lily_of_the_valley", "wither_rose", + "sunflower", "lilac", "rose_bush", "peony"); + + /** + * All entity types that can be leashed on Java Edition + */ + private static final List LEASHABLE_MOB_TYPES = Arrays.asList(EntityType.BEE, EntityType.CAT, EntityType.CHICKEN, + EntityType.COW, EntityType.DOLPHIN, EntityType.DONKEY, EntityType.FOX, 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.STRIDER, EntityType.WOLF, EntityType.ZOGLIN); + + private static final List SADDLEABLE_WHEN_TAMED_MOB_TYPES = Arrays.asList(EntityType.DONKEY, EntityType.HORSE, + EntityType.ZOMBIE_HORSE, EntityType.MULE); + /** + * A list of all foods a wolf can eat on Java Edition. + * Used to display interactive tag if needed. + */ + private static final List WOLF_FOODS = Arrays.asList("pufferfish", "tropical_fish", "chicken", "cooked_chicken", + "porkchop", "beef", "rabbit", "cooked_porkchop", "cooked_beef", "rotten_flesh", "mutton", "cooked_mutton", + "cooked_rabbit"); + @Override public void translate(InteractPacket packet, GeyserSession session) { Entity entity; @@ -84,50 +122,232 @@ public class BedrockInteractTranslator extends PacketTranslator if (interactEntity == null) return; EntityDataMap entityMetadata = interactEntity.getMetadata(); + ItemEntry itemEntry = session.getInventory().getItemInHand() == null ? ItemEntry.AIR : ItemRegistry.getItem(session.getInventory().getItemInHand()); + String javaIdentifierStripped = itemEntry.getJavaIdentifier().replace("minecraft:", ""); - String interactiveTag; - switch (interactEntity.getEntityType()) { - case BOAT: - interactiveTag = "action.interact.ride.boat"; - break; - case DONKEY: - case HORSE: - case LLAMA: - case MULE: - case SKELETON_HORSE: - case TRADER_LLAMA: - case ZOMBIE_HORSE: - if (entityMetadata.getFlags().getFlag(EntityFlag.TAMED)) { - interactiveTag = "action.interact.ride.horse"; - } else { - interactiveTag = "action.interact.mount"; - } - break; - case MINECART: - interactiveTag = "action.interact.ride.minecart"; - break; - case PIG: - if (entityMetadata.getFlags().getFlag(EntityFlag.SADDLED)) { - interactiveTag = "action.interact.mount"; - } else interactiveTag = ""; - break; - case VILLAGER: - if (entityMetadata.getInt(EntityData.VARIANT) != 14 && entityMetadata.getInt(EntityData.VARIANT) != 0 - && entityMetadata.getFloat(EntityData.SCALE) >= 0.75f) { // Not a nitwit, has a profession and is not a baby - interactiveTag = "action.interact.trade"; - } else interactiveTag = ""; - break; - case WANDERING_TRADER: - interactiveTag = "action.interact.trade"; // Since you can always trade with a wandering villager, presumably. - break; - default: - return; // No need to process any further since there is no interactive tag + // TODO - in the future, update these in the metadata? So the client doesn't have to wiggle their cursor around for it to happen + // TODO - also, might be good to abstract out the eating thing. I know there will need to be food tracked for https://github.com/GeyserMC/Geyser/issues/1005 but not all food is breeding food + InteractiveTag interactiveTag = InteractiveTag.NONE; + if (entityMetadata.getLong(EntityData.LEASH_HOLDER_EID) == session.getPlayerEntity().getGeyserId()) { + // Unleash the entity + interactiveTag = InteractiveTag.REMOVE_LEASH; + } else if (javaIdentifierStripped.equals("saddle") && !entityMetadata.getFlags().getFlag(EntityFlag.SADDLED) && + ((SADDLEABLE_WHEN_TAMED_MOB_TYPES.contains(interactEntity.getEntityType()) && entityMetadata.getFlags().getFlag(EntityFlag.TAMED)) || + interactEntity.getEntityType() == EntityType.PIG || interactEntity.getEntityType() == 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.getInventory().getItemInHand().getNbt() != null && + session.getInventory().getItemInHand().getNbt().contains("display")) { + // Holding a named name tag + interactiveTag = InteractiveTag.NAME; + } else if (javaIdentifierStripped.equals("lead") && LEASHABLE_MOB_TYPES.contains(interactEntity.getEntityType()) && + entityMetadata.getLong(EntityData.LEASH_HOLDER_EID) == -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 { + switch (interactEntity.getEntityType()) { + case BEE: + if (FLOWERS.contains(javaIdentifierStripped)) { + interactiveTag = InteractiveTag.FEED; + } + break; + case BOAT: + interactiveTag = InteractiveTag.BOARD_BOAT; + break; + case CAT: + if (javaIdentifierStripped.equals("cod") || javaIdentifierStripped.equals("salmon")) { + interactiveTag = InteractiveTag.FEED; + } else if (entityMetadata.getFlags().getFlag(EntityFlag.TAMED) && + entityMetadata.getLong(EntityData.OWNER_EID) == session.getPlayerEntity().getGeyserId()) { + // Tamed and owned by player - can sit/stand + interactiveTag = entityMetadata.getFlags().getFlag(EntityFlag.SITTING) ? InteractiveTag.STAND : InteractiveTag.SIT; + break; + } + break; + case CHICKEN: + if (javaIdentifierStripped.contains("seeds")) { + interactiveTag = InteractiveTag.FEED; + } + 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("wheat")) { + interactiveTag = InteractiveTag.FEED; + } else 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 (entityMetadata.getFlags().getFlag(EntityFlag.TAMED) && !entityMetadata.getFlags().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: + // have another switch statement as, while these share mount attributes they don't share food + switch (interactEntity.getEntityType()) { + case LLAMA: + case TRADER_LLAMA: + if (javaIdentifierStripped.equals("wheat") || javaIdentifierStripped.equals("hay_block")) { + interactiveTag = InteractiveTag.FEED; + break; + } + case DONKEY: + case HORSE: + // Undead can't eat + if (DONKEY_AND_HORSE_FOODS.contains(javaIdentifierStripped)) { + interactiveTag = InteractiveTag.FEED; + break; + } + } + if (!entityMetadata.getFlags().getFlag(EntityFlag.BABY)) { + // Can't ride a baby + if (entityMetadata.getFlags().getFlag(EntityFlag.TAMED)) { + interactiveTag = InteractiveTag.RIDE_HORSE; + } else if (!entityMetadata.getFlags().getFlag(EntityFlag.TAMED) && itemEntry.equals(ItemEntry.AIR)) { + // Can't hide an untamed entity without having your hand empty + interactiveTag = InteractiveTag.MOUNT; + } + } + break; + case FOX: + if (javaIdentifierStripped.equals("sweet_berries")) { + interactiveTag = InteractiveTag.FEED; + } + break; + case HOGLIN: + if (javaIdentifierStripped.equals("crimson_fungus")) { + interactiveTag = InteractiveTag.FEED; + } + break; + case MINECART: + interactiveTag = InteractiveTag.RIDE_MINECART; + break; + case MINECART_CHEST: + case MINECART_COMMAND_BLOCK: + case MINECART_HOPPER: + interactiveTag = InteractiveTag.OPEN_CONTAINER; + break; + case OCELOT: + if (javaIdentifierStripped.equals("cod") || javaIdentifierStripped.equals("salmon")) { + interactiveTag = InteractiveTag.FEED; + } + break; + case PANDA: + if (javaIdentifierStripped.equals("bamboo")) { + interactiveTag = InteractiveTag.FEED; + } + break; + case PARROT: + if (javaIdentifierStripped.contains("seeds") || javaIdentifierStripped.equals("cookie")) { + interactiveTag = InteractiveTag.FEED; + } + break; + case PIG: + if (javaIdentifierStripped.equals("carrot") || javaIdentifierStripped.equals("potato") || javaIdentifierStripped.equals("beetroot")) { + interactiveTag = InteractiveTag.FEED; + } else if (entityMetadata.getFlags().getFlag(EntityFlag.SADDLED)) { + interactiveTag = InteractiveTag.MOUNT; + } + break; + case PIGLIN: + if (!entityMetadata.getFlags().getFlag(EntityFlag.BABY) && javaIdentifierStripped.equals("gold_ingot")) { + interactiveTag = InteractiveTag.BARTER; + } + break; + case RABBIT: + if (javaIdentifierStripped.equals("dandelion") || javaIdentifierStripped.equals("carrot") || javaIdentifierStripped.equals("golden_carrot")) { + interactiveTag = InteractiveTag.FEED; + } + break; + case SHEEP: + if (javaIdentifierStripped.equals("wheat")) { + interactiveTag = InteractiveTag.FEED; + } else if (!entityMetadata.getFlags().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 (javaIdentifierStripped.equals("warped_fungus")) { + interactiveTag = InteractiveTag.FEED; + } else if (entityMetadata.getFlags().getFlag(EntityFlag.SADDLED)) { + interactiveTag = InteractiveTag.RIDE_STRIDER; + } + break; + case TURTLE: + if (javaIdentifierStripped.equals("seagrass")) { + interactiveTag = InteractiveTag.FEED; + } + break; + case VILLAGER: + if (entityMetadata.getInt(EntityData.VARIANT) != 14 && entityMetadata.getInt(EntityData.VARIANT) != 0 + && entityMetadata.getFloat(EntityData.SCALE) >= 0.75f) { // 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") && !entityMetadata.getFlags().getFlag(EntityFlag.TAMED)) { + // Bone and untamed - can tame + interactiveTag = InteractiveTag.TAME; + } else if (WOLF_FOODS.contains(javaIdentifierStripped)) { + // Compatible food in hand - feed + // Sometimes just sits/stands when the wolf isn't hungry - there doesn't appear to be a way to fix this + interactiveTag = InteractiveTag.FEED; + } else if (entityMetadata.getFlags().getFlag(EntityFlag.TAMED) && + entityMetadata.getLong(EntityData.OWNER_EID) == session.getPlayerEntity().getGeyserId()) { + // Tamed and owned by player - can sit/stand + interactiveTag = entityMetadata.getFlags().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().getMetadata().put(EntityData.INTERACTIVE_TAG, interactiveTag); + session.getPlayerEntity().getMetadata().put(EntityData.INTERACTIVE_TAG, interactiveTag.getValue()); session.getPlayerEntity().updateBedrockMetadata(session); } else { - if (!(session.getPlayerEntity().getMetadata().get(EntityData.INTERACTIVE_TAG) == null) || - !(session.getPlayerEntity().getMetadata().get(EntityData.INTERACTIVE_TAG) == "")) { + if (!session.getPlayerEntity().getMetadata().getString(EntityData.INTERACTIVE_TAG).isEmpty()) { // No interactive tag should be sent session.getPlayerEntity().getMetadata().remove(EntityData.INTERACTIVE_TAG); session.getPlayerEntity().updateBedrockMetadata(session); @@ -147,4 +367,65 @@ public class BedrockInteractTranslator extends PacketTranslator break; } } + + /** + * All interactive tags in enum form. For potential API usage. + */ + public enum InteractiveTag { + NONE(true), + 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(boolean isNone) { + this.value = ""; + } + + InteractiveTag(String value) { + this.value = "action.interact." + value; + } + + InteractiveTag() { + this.value = "action.interact." + name().toLowerCase(); + } + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java index 8370ba8e..18103087 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java @@ -55,6 +55,10 @@ public class ItemRegistry { public static final List ITEMS = new ArrayList<>(); public static final Int2ObjectMap ITEM_ENTRIES = new Int2ObjectOpenHashMap<>(); + /** + * Bamboo item entry, used in PandaEntity.java + */ + public static ItemEntry BAMBOO; /** * Boat item entry, used in BedrockInventoryTransactionTranslator.java */ @@ -146,6 +150,9 @@ public class ItemRegistry { case "minecraft:barrier": BARRIER_INDEX = itemIndex; break; + case "minecraft:bamboo": + BAMBOO = ITEM_ENTRIES.get(itemIndex); + break; case "minecraft:oak_boat": BOAT = ITEM_ENTRIES.get(itemIndex); break; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java index a86c1a97..35ca7928 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java @@ -55,7 +55,7 @@ public class JavaJoinGameTranslator extends PacketTranslator } String newDimension = DimensionUtils.getNewDimension(packet.getDimension()); - if (!entity.getDimension().equals(newDimension)) { + if (!session.getDimension().equals(newDimension)) { DimensionUtils.switchDimension(session, newDimension); } else { if (session.isManyDimPackets()) { //reloading world - String fakeDim = entity.getDimension().equals(DimensionUtils.OVERWORLD) ? DimensionUtils.NETHER : DimensionUtils.OVERWORLD; + String fakeDim = session.getDimension().equals(DimensionUtils.OVERWORLD) ? DimensionUtils.NETHER : DimensionUtils.OVERWORLD; DimensionUtils.switchDimension(session, fakeDim); DimensionUtils.switchDimension(session, newDimension); } else { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityAttachTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityAttachTranslator.java index b642a75b..1a6630ef 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityAttachTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityAttachTranslator.java @@ -62,7 +62,7 @@ public class JavaEntityAttachTranslator extends PacketTranslator { @@ -141,9 +137,15 @@ public class JavaEntityStatusTranslator extends PacketTranslator boolean shouldStore = false; mapItemDataPacket.setUniqueMapId(packet.getMapId()); - mapItemDataPacket.setDimensionId(DimensionUtils.javaToBedrock(session.getPlayerEntity().getDimension())); + mapItemDataPacket.setDimensionId(DimensionUtils.javaToBedrock(session.getDimension())); mapItemDataPacket.setLocked(packet.isLocked()); mapItemDataPacket.setScale(packet.getScale()); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaSpawnParticleTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaSpawnParticleTranslator.java index 52d3f649..4620fc11 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaSpawnParticleTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaSpawnParticleTranslator.java @@ -94,7 +94,7 @@ public class JavaSpawnParticleTranslator extends PacketTranslator localeStrings = LocaleUtils.LOCALE_MAPPINGS.get(locale.toLowerCase()); - if (localeStrings == null) - localeStrings = LocaleUtils.LOCALE_MAPPINGS.get(LanguageUtils.getDefaultLocale()); if (localeStrings == null) { - // Don't cause a NPE if the locale is STILL missing - GeyserConnector.getInstance().getLogger().debug("MISSING DEFAULT LOCALE: " + LanguageUtils.getDefaultLocale()); - return messageText; + localeStrings = LocaleUtils.LOCALE_MAPPINGS.get(LanguageUtils.getDefaultLocale()); + if (localeStrings == null) { + // Don't cause a NPE if the locale is STILL missing + GeyserConnector.getInstance().getLogger().debug("MISSING DEFAULT LOCALE: " + LanguageUtils.getDefaultLocale()); + return messageText; + } } return localeStrings.getOrDefault(messageText, messageText); From 80cf407faee7306252e600ab7cf9563229ee8088 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Fri, 13 Nov 2020 23:00:09 -0500 Subject: [PATCH 036/116] Update MCProtocolLib to fix more custom recipe stuff (#1534) --- connector/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/pom.xml b/connector/pom.xml index ee3164e1..d837f057 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -111,7 +111,7 @@ com.github.steveice10 mcprotocollib - 6ac79c14d6 + 86e1901be5 compile From bf05f64fac2cedcb8bb41f6c44a50581029dea6b Mon Sep 17 00:00:00 2001 From: Niklas <7442307+niklaswa@users.noreply.github.com> Date: Sat, 14 Nov 2020 18:52:10 +0100 Subject: [PATCH 037/116] Update LabyMod cape url (#1540) --- .../main/java/org/geysermc/connector/utils/SkinProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java b/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java index 82fc3a3a..d848d95e 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java @@ -607,7 +607,7 @@ public class SkinProvider { public enum CapeProvider { MINECRAFT, OPTIFINE("https://optifine.net/capes/%s.png", CapeUrlType.USERNAME), - LABYMOD("https://www.labymod.net/page/php/getCapeTexture.php?uuid=%s", CapeUrlType.UUID_DASHED), + LABYMOD("https://dl.labymod.net/capes/%s", CapeUrlType.UUID_DASHED), FIVEZIG("https://textures.5zigreborn.eu/profile/%s", CapeUrlType.UUID_DASHED), MINECRAFTCAPES("https://minecraftcapes.net/profile/%s/cape", CapeUrlType.UUID); From 445444204f017ba4b704478efc4a97c398da2ed2 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Sat, 14 Nov 2020 13:05:33 -0500 Subject: [PATCH 038/116] Fix GlobalPalette translation (#1528) * Fix GlobalPalette translation Global palettes don't have their own internal palette, which cannot be iterated through to create a Bedrock palette. Therefore we simply iterate over the whole palette one time. This commit also fixes a regression with flowers/pistons being on multiple chunk sections. * Don't declare bedrockPalette until after global palette check --- .../geysermc/connector/utils/ChunkUtils.java | 37 ++++++++++++++++--- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java index 0769a4d1..70ac76df 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java @@ -28,6 +28,7 @@ package org.geysermc.connector.utils; import com.github.steveice10.mc.protocol.data.game.chunk.BitStorage; import com.github.steveice10.mc.protocol.data.game.chunk.Chunk; import com.github.steveice10.mc.protocol.data.game.chunk.Column; +import com.github.steveice10.mc.protocol.data.game.chunk.palette.GlobalPalette; import com.github.steveice10.mc.protocol.data.game.chunk.palette.Palette; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; @@ -65,10 +66,10 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.BitSet; -import java.util.Collections; import java.util.List; -import static org.geysermc.connector.network.translators.world.block.BlockTranslator.*; +import static org.geysermc.connector.network.translators.world.block.BlockTranslator.AIR; +import static org.geysermc.connector.network.translators.world.block.BlockTranslator.BEDROCK_WATER_ID; @UtilityClass public class ChunkUtils { @@ -107,7 +108,7 @@ public class ChunkUtils { ChunkSection[] sections = new ChunkSection[javaSections.length]; // Temporarily stores compound tags of Bedrock-only block entities - List bedrockOnlyBlockEntities = Collections.emptyList(); + List bedrockOnlyBlockEntities = new ArrayList<>(); BitSet waterloggedPaletteIds = new BitSet(); BitSet pistonOrFlowerPaletteIds = new BitSet(); @@ -155,6 +156,33 @@ public class ChunkUtils { } Palette javaPalette = javaSection.getPalette(); + BitStorage javaData = javaSection.getStorage(); + + if (javaPalette instanceof GlobalPalette) { + // As this is the global palette, simply iterate through the whole chunk section once + ChunkSection section = new ChunkSection(); + for (int yzx = 0; yzx < BlockStorage.SIZE; yzx++) { + int javaId = javaData.get(yzx); + int bedrockId = BlockTranslator.getBedrockBlockId(javaId); + int xzy = indexYZXtoXZY(yzx); + section.getBlockStorageArray()[0].setFullBlock(xzy, bedrockId); + + if (BlockTranslator.isWaterlogged(javaId)) { + section.getBlockStorageArray()[1].setFullBlock(xzy, BEDROCK_WATER_ID); + } + + // Check if block is piston or flower to see if we'll need to create additional block entities, as they're only block entities in Bedrock + if (BlockStateValues.getFlowerPotValues().containsKey(javaId) || BlockStateValues.getPistonValues().containsKey(javaId)) { + bedrockOnlyBlockEntities.add(BedrockOnlyBlockEntity.getTag( + Vector3i.from((column.getX() << 4) + (yzx & 0xF), (sectionY << 4) + ((yzx >> 8) & 0xF), (column.getZ() << 4) + ((yzx >> 4) & 0xF)), + javaId + )); + } + } + sections[sectionY] = section; + continue; + } + IntList bedrockPalette = new IntArrayList(javaPalette.size()); waterloggedPaletteIds.clear(); pistonOrFlowerPaletteIds.clear(); @@ -174,13 +202,10 @@ public class ChunkUtils { } } - BitStorage javaData = javaSection.getStorage(); - // Add Bedrock-exclusive block entities // We only if the palette contained any blocks that are Bedrock-exclusive block entities to avoid iterating through the whole block data // for no reason, as most sections will not contain any pistons or flower pots if (!pistonOrFlowerPaletteIds.isEmpty()) { - bedrockOnlyBlockEntities = new ArrayList<>(); for (int yzx = 0; yzx < BlockStorage.SIZE; yzx++) { int paletteId = javaData.get(yzx); if (pistonOrFlowerPaletteIds.get(paletteId)) { From 981ac3bf118f331ded9fe4bcbd0e2f9e90792750 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sat, 14 Nov 2020 17:49:56 -0600 Subject: [PATCH 039/116] Move PlatformType to common module --- .../org/geysermc/platform/bungeecord/GeyserBungeePlugin.java | 2 +- .../java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java | 2 +- .../java/org/geysermc/platform/sponge/GeyserSpongePlugin.java | 2 +- .../geysermc/platform/standalone/GeyserStandaloneBootstrap.java | 2 +- .../org/geysermc/platform/velocity/GeyserVelocityPlugin.java | 2 +- .../src/main/java/org/geysermc}/common/PlatformType.java | 2 +- .../src/main/java/org/geysermc/connector/GeyserConnector.java | 2 +- .../org/geysermc/connector/command/defaults/ReloadCommand.java | 2 +- .../org/geysermc/connector/command/defaults/StopCommand.java | 2 +- .../java/org/geysermc/connector/dump/BootstrapDumpInfo.java | 2 +- .../translators/bedrock/BedrockCommandRequestTranslator.java | 2 +- .../translators/java/world/JavaBlockChangeTranslator.java | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) rename {connector/src/main/java/org/geysermc/connector => common/src/main/java/org/geysermc}/common/PlatformType.java (97%) diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java index abb9789e..a65646bf 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java @@ -27,10 +27,10 @@ package org.geysermc.platform.bungeecord; import net.md_5.bungee.api.config.ListenerInfo; import net.md_5.bungee.api.plugin.Plugin; +import org.geysermc.common.PlatformType; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.bootstrap.GeyserBootstrap; import org.geysermc.connector.command.CommandManager; -import org.geysermc.connector.common.PlatformType; import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.dump.BootstrapDumpInfo; import org.geysermc.connector.ping.GeyserLegacyPingPassthrough; diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java index 59a8db06..741763ec 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java @@ -27,10 +27,10 @@ package org.geysermc.platform.spigot; import org.bukkit.Bukkit; import org.bukkit.plugin.java.JavaPlugin; +import org.geysermc.common.PlatformType; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.bootstrap.GeyserBootstrap; import org.geysermc.connector.command.CommandManager; -import org.geysermc.connector.common.PlatformType; import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.dump.BootstrapDumpInfo; import org.geysermc.connector.network.translators.world.WorldManager; diff --git a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java index 106d2b15..c3231f3b 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java @@ -26,10 +26,10 @@ package org.geysermc.platform.sponge; import com.google.inject.Inject; +import org.geysermc.common.PlatformType; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.bootstrap.GeyserBootstrap; import org.geysermc.connector.command.CommandManager; -import org.geysermc.connector.common.PlatformType; import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.dump.BootstrapDumpInfo; import org.geysermc.connector.ping.GeyserLegacyPingPassthrough; diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java index f4dfd454..a0a8a3ae 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java @@ -36,10 +36,10 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.core.Appender; import org.apache.logging.log4j.core.Logger; import org.apache.logging.log4j.core.appender.ConsoleAppender; +import org.geysermc.common.PlatformType; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.bootstrap.GeyserBootstrap; import org.geysermc.connector.command.CommandManager; -import org.geysermc.connector.common.PlatformType; import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.configuration.GeyserJacksonConfiguration; import org.geysermc.connector.dump.BootstrapDumpInfo; diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java index f75c683a..b5255e62 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java @@ -33,9 +33,9 @@ import com.velocitypowered.api.event.proxy.ProxyShutdownEvent; import com.velocitypowered.api.plugin.Plugin; import com.velocitypowered.api.proxy.ProxyServer; import lombok.Getter; +import org.geysermc.common.PlatformType; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.bootstrap.GeyserBootstrap; -import org.geysermc.connector.common.PlatformType; import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.dump.BootstrapDumpInfo; import org.geysermc.connector.ping.GeyserLegacyPingPassthrough; diff --git a/connector/src/main/java/org/geysermc/connector/common/PlatformType.java b/common/src/main/java/org/geysermc/common/PlatformType.java similarity index 97% rename from connector/src/main/java/org/geysermc/connector/common/PlatformType.java rename to common/src/main/java/org/geysermc/common/PlatformType.java index 3e945d3a..88349020 100644 --- a/connector/src/main/java/org/geysermc/connector/common/PlatformType.java +++ b/common/src/main/java/org/geysermc/common/PlatformType.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.common; +package org.geysermc.common; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 2497166c..c461fa96 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -32,10 +32,10 @@ import com.nukkitx.network.raknet.RakNetConstants; import com.nukkitx.protocol.bedrock.BedrockServer; import lombok.Getter; import lombok.Setter; +import org.geysermc.common.PlatformType; import org.geysermc.connector.bootstrap.GeyserBootstrap; import org.geysermc.connector.command.CommandManager; import org.geysermc.connector.common.AuthType; -import org.geysermc.connector.common.PlatformType; import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.metrics.Metrics; import org.geysermc.connector.network.ConnectorServerEventHandler; diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/ReloadCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/ReloadCommand.java index 6b2be294..64d0017e 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/ReloadCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/ReloadCommand.java @@ -25,7 +25,7 @@ package org.geysermc.connector.command.defaults; -import org.geysermc.connector.common.PlatformType; +import org.geysermc.common.PlatformType; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.CommandSender; import org.geysermc.connector.command.GeyserCommand; diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/StopCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/StopCommand.java index c69a8705..5be6253e 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/StopCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/StopCommand.java @@ -25,7 +25,7 @@ package org.geysermc.connector.command.defaults; -import org.geysermc.connector.common.PlatformType; +import org.geysermc.common.PlatformType; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.CommandSender; import org.geysermc.connector.command.GeyserCommand; diff --git a/connector/src/main/java/org/geysermc/connector/dump/BootstrapDumpInfo.java b/connector/src/main/java/org/geysermc/connector/dump/BootstrapDumpInfo.java index 9fbe82cc..58556553 100644 --- a/connector/src/main/java/org/geysermc/connector/dump/BootstrapDumpInfo.java +++ b/connector/src/main/java/org/geysermc/connector/dump/BootstrapDumpInfo.java @@ -27,7 +27,7 @@ package org.geysermc.connector.dump; import lombok.AllArgsConstructor; import lombok.Getter; -import org.geysermc.connector.common.PlatformType; +import org.geysermc.common.PlatformType; import org.geysermc.connector.GeyserConnector; import java.util.List; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandRequestTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandRequestTranslator.java index d05a667d..1f31367c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandRequestTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandRequestTranslator.java @@ -25,7 +25,7 @@ package org.geysermc.connector.network.translators.bedrock; -import org.geysermc.connector.common.PlatformType; +import org.geysermc.common.PlatformType; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.CommandManager; import org.geysermc.connector.network.session.GeyserSession; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockChangeTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockChangeTranslator.java index 9e81ce59..50705ae2 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockChangeTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockChangeTranslator.java @@ -29,7 +29,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.SoundEvent; import com.nukkitx.protocol.bedrock.packet.LevelSoundEventPacket; -import org.geysermc.connector.common.PlatformType; +import org.geysermc.common.PlatformType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; From 47f25f1205f360a690a7510b2e79fc7c30ead61a Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Sun, 15 Nov 2020 18:48:11 -0500 Subject: [PATCH 040/116] BannerTranslator: fix NPE when no block entity data (#1543) --- .../network/translators/item/translators/BannerTranslator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java index 200271cf..14b93436 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java @@ -130,7 +130,7 @@ public class BannerTranslator extends ItemTranslator { ItemData itemData = super.translateToBedrock(itemStack, itemEntry); CompoundTag blockEntityTag = itemStack.getNbt().get("BlockEntityTag"); - if (blockEntityTag.contains("Patterns")) { + if (blockEntityTag != null && blockEntityTag.contains("Patterns")) { ListTag patterns = blockEntityTag.get("Patterns"); NbtMapBuilder builder = itemData.getTag().toBuilder(); From 512f8cd6c2cf002aaf8e3087c361784b8c603fd2 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Mon, 16 Nov 2020 23:57:57 +0000 Subject: [PATCH 041/116] Rewrite message handling in MessageUtils to use Adventure (#1498) * Rewrite message handling in MessageUtils to use Adventure * Move to static Adventure commit to fix a bug * Initial test implementation * Add RGB downgrade test * Move MessageUtils and rename * Clean-up and fix tests * Fixed sign and book content handling * Fix blank signs causing NPEs * Fix reset before message being stripped * Add comment about the reset character * Fix legacy style server motds * Fix more messages being handled wrong * Fix title packets being handled wrong * Fix trailing formatting characters on the end of sign lines * Add auto updating of Java locale files * Add en_us locale updating and hash caching * Changes to hash determining Co-authored-by: DoctorMacc --- connector/pom.xml | 23 +- .../entity/CommandBlockMinecartEntity.java | 5 +- .../org/geysermc/connector/entity/Entity.java | 4 +- .../connector/entity/PlayerEntity.java | 6 +- .../network/ConnectorServerEventHandler.java | 5 +- .../connector/network/QueryPacketHandler.java | 5 +- .../network/session/GeyserSession.java | 4 +- .../network/session/cache/BossBar.java | 6 +- .../BedrockCommandRequestTranslator.java | 4 +- .../bedrock/BedrockTextTranslator.java | 4 +- .../translators/chat/MessageTranslator.java | 278 ++++++++++ .../chat/MinecraftTranslationRegistry.java | 81 +++ .../translators/item/ItemTranslator.java | 26 +- .../translators/nbt/BasicItemTranslator.java | 3 +- .../translators/nbt/BookPagesTranslator.java | 6 +- .../translators/java/JavaChatTranslator.java | 22 +- .../java/JavaDisconnectPacket.java | 4 +- .../java/JavaLoginDisconnectTranslator.java | 4 +- .../translators/java/JavaTitleTranslator.java | 16 +- .../JavaScoreboardObjectiveTranslator.java | 4 +- .../java/scoreboard/JavaTeamTranslator.java | 14 +- .../java/window/JavaOpenWindowTranslator.java | 6 +- .../CommandBlockBlockEntityTranslator.java | 4 +- .../entity/SignBlockEntityTranslator.java | 16 +- .../geysermc/connector/utils/FileUtils.java | 21 +- .../geysermc/connector/utils/LocaleUtils.java | 52 +- .../connector/utils/MessageUtils.java | 494 ------------------ .../chat/MessageTranslatorTest.java | 67 +++ 28 files changed, 583 insertions(+), 601 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/chat/MessageTranslator.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/chat/MinecraftTranslationRegistry.java delete mode 100644 connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java create mode 100644 connector/src/test/java/org/geysermc/connector/network/translators/chat/MessageTranslatorTest.java diff --git a/connector/pom.xml b/connector/pom.xml index d837f057..db267a71 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -143,17 +143,23 @@ compile - net.kyori + com.github.kyoripowered.adventure adventure-text-serializer-gson - 4.1.1 + 4d8a67d798 compile - net.kyori + com.github.kyoripowered.adventure adventure-text-serializer-legacy - 4.1.1 + 0599048 compile + + junit + junit + 4.13.1 + test + @@ -283,6 +289,15 @@ + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.0 + + + -Dfile.encoding=${project.build.sourceEncoding} + + diff --git a/connector/src/main/java/org/geysermc/connector/entity/CommandBlockMinecartEntity.java b/connector/src/main/java/org/geysermc/connector/entity/CommandBlockMinecartEntity.java index 8cabba64..7d34cc79 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/CommandBlockMinecartEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/CommandBlockMinecartEntity.java @@ -26,13 +26,12 @@ package org.geysermc.connector.entity; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.github.steveice10.mc.protocol.data.message.Message; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.block.BlockTranslator; -import org.geysermc.connector.utils.MessageUtils; +import org.geysermc.connector.network.translators.chat.MessageTranslator; public class CommandBlockMinecartEntity extends DefaultBlockMinecartEntity { @@ -51,7 +50,7 @@ public class CommandBlockMinecartEntity extends DefaultBlockMinecartEntity { metadata.put(EntityData.COMMAND_BLOCK_COMMAND, entityMetadata.getValue()); } if (entityMetadata.getId() == 14) { - metadata.put(EntityData.COMMAND_BLOCK_LAST_OUTPUT, MessageUtils.getBedrockMessage((Message) entityMetadata.getValue())); + metadata.put(EntityData.COMMAND_BLOCK_LAST_OUTPUT, MessageTranslator.convertMessage(entityMetadata.getValue().toString())); } super.updateBedrockMetadata(entityMetadata, session); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/Entity.java b/connector/src/main/java/org/geysermc/connector/entity/Entity.java index 20cd2f76..7b1fa1cf 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/Entity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/Entity.java @@ -54,7 +54,7 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.utils.AttributeUtils; import org.geysermc.connector.utils.ChunkUtils; -import org.geysermc.connector.utils.MessageUtils; +import org.geysermc.connector.network.translators.chat.MessageTranslator; import java.util.ArrayList; import java.util.HashMap; @@ -318,7 +318,7 @@ public class Entity { Message message = (Message) entityMetadata.getValue(); if (message != null) // Always translate even if it's a TextMessage since there could be translatable parameters - metadata.put(EntityData.NAMETAG, MessageUtils.getTranslatedBedrockMessage(message, session.getLocale(), true)); + metadata.put(EntityData.NAMETAG, MessageTranslator.convertMessage(message.toString(), session.getLocale())); } break; case 3: // is custom name visible diff --git a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java index 8eeae473..be65525c 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java @@ -51,7 +51,7 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.cache.EntityEffectCache; import org.geysermc.connector.scoreboard.Team; import org.geysermc.connector.utils.AttributeUtils; -import org.geysermc.connector.utils.MessageUtils; +import org.geysermc.connector.network.translators.chat.MessageTranslator; import java.util.ArrayList; import java.util.List; @@ -243,13 +243,13 @@ public class PlayerEntity extends LivingEntity { String username = this.username; TextMessage name = (TextMessage) entityMetadata.getValue(); if (name != null) { - username = MessageUtils.getBedrockMessage(name); + username = MessageTranslator.convertMessage(name.toString()); } Team team = session.getWorldCache().getScoreboard().getTeamFor(username); if (team != null) { String displayName = ""; if (team.isVisibleFor(session.getPlayerEntity().getUsername())) { - displayName = MessageUtils.toChatColor(team.getColor()) + username; + displayName = MessageTranslator.toChatColor(team.getColor()) + username; displayName = team.getCurrentData().getDisplayName(displayName); } metadata.put(EntityData.NAMETAG, displayName); diff --git a/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java b/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java index 9fb4ad9e..150d298c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java @@ -25,7 +25,6 @@ package org.geysermc.connector.network; -import com.github.steveice10.mc.protocol.data.message.MessageSerializer; import com.nukkitx.protocol.bedrock.BedrockPong; import com.nukkitx.protocol.bedrock.BedrockServerEventHandler; import com.nukkitx.protocol.bedrock.BedrockServerSession; @@ -36,7 +35,7 @@ import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.ping.IGeyserPingPassthrough; -import org.geysermc.connector.utils.MessageUtils; +import org.geysermc.connector.network.translators.chat.MessageTranslator; import org.geysermc.connector.utils.LanguageUtils; import java.net.InetSocketAddress; @@ -76,7 +75,7 @@ public class ConnectorServerEventHandler implements BedrockServerEventHandler { pong.setIpv4Port(config.getBedrock().getPort()); if (config.isPassthroughMotd() && pingInfo != null && pingInfo.getDescription() != null) { - String[] motd = MessageUtils.getBedrockMessage(MessageSerializer.fromString(pingInfo.getDescription())).split("\n"); + String[] motd = MessageTranslator.convertMessageLenient(pingInfo.getDescription()).split("\n"); String mainMotd = motd[0]; // First line of the motd. String subMotd = (motd.length != 1) ? motd[1] : ""; // Second line of the motd if present, otherwise blank. diff --git a/connector/src/main/java/org/geysermc/connector/network/QueryPacketHandler.java b/connector/src/main/java/org/geysermc/connector/network/QueryPacketHandler.java index 7faf36bd..510bba2d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/QueryPacketHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/QueryPacketHandler.java @@ -25,12 +25,11 @@ package org.geysermc.connector.network; -import com.github.steveice10.mc.protocol.data.message.MessageSerializer; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import org.geysermc.connector.common.ping.GeyserPingInfo; import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.utils.MessageUtils; +import org.geysermc.connector.network.translators.chat.MessageTranslator; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -148,7 +147,7 @@ public class QueryPacketHandler { } if (connector.getConfig().isPassthroughMotd() && pingInfo != null) { - String[] javaMotd = MessageUtils.getBedrockMessage(MessageSerializer.fromString(pingInfo.getDescription())).split("\n"); + String[] javaMotd = MessageTranslator.convertMessageLenient(pingInfo.getDescription()).split("\n"); motd = javaMotd[0].trim(); // First line of the motd. } else { motd = connector.getConfig().getBedrock().getMotd1(); diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index a6085e21..00b48a56 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -34,7 +34,6 @@ import com.github.steveice10.mc.protocol.data.SubProtocol; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import com.github.steveice10.mc.protocol.data.game.statistic.Statistic; import com.github.steveice10.mc.protocol.data.game.window.VillagerTrade; -import com.github.steveice10.mc.protocol.data.message.MessageSerializer; import com.github.steveice10.mc.protocol.packet.handshake.client.HandshakePacket; import com.github.steveice10.mc.protocol.packet.ingame.client.world.ClientTeleportConfirmPacket; import com.github.steveice10.mc.protocol.packet.ingame.server.ServerRespawnPacket; @@ -66,6 +65,7 @@ import org.geysermc.connector.common.AuthType; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.PlayerEntity; import org.geysermc.connector.inventory.PlayerInventory; +import org.geysermc.connector.network.translators.chat.MessageTranslator; import org.geysermc.connector.network.remote.RemoteServer; import org.geysermc.connector.network.session.auth.AuthData; import org.geysermc.connector.network.session.auth.BedrockClientData; @@ -496,7 +496,7 @@ public class GeyserSession implements CommandSender { event.getCause().printStackTrace(); } - upstream.disconnect(MessageUtils.getBedrockMessage(MessageSerializer.fromString(event.getReason()))); + upstream.disconnect(MessageTranslator.convertMessageLenient(event.getReason())); } @Override diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/BossBar.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/BossBar.java index fdc609ab..7eadb794 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/BossBar.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/BossBar.java @@ -33,7 +33,7 @@ import com.nukkitx.protocol.bedrock.packet.BossEventPacket; import com.nukkitx.protocol.bedrock.packet.RemoveEntityPacket; import lombok.AllArgsConstructor; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.utils.MessageUtils; +import org.geysermc.connector.network.translators.chat.MessageTranslator; @AllArgsConstructor public class BossBar { @@ -58,7 +58,7 @@ public class BossBar { BossEventPacket bossEventPacket = new BossEventPacket(); bossEventPacket.setBossUniqueEntityId(entityId); bossEventPacket.setAction(BossEventPacket.Action.CREATE); - bossEventPacket.setTitle(MessageUtils.getTranslatedBedrockMessage(title, session.getLocale())); + bossEventPacket.setTitle(MessageTranslator.convertMessage(title.toString(), session.getLocale())); bossEventPacket.setHealthPercentage(health); bossEventPacket.setColor(color); //ignored by client bossEventPacket.setOverlay(overlay); @@ -72,7 +72,7 @@ public class BossBar { BossEventPacket bossEventPacket = new BossEventPacket(); bossEventPacket.setBossUniqueEntityId(entityId); bossEventPacket.setAction(BossEventPacket.Action.UPDATE_NAME); - bossEventPacket.setTitle(MessageUtils.getTranslatedBedrockMessage(title, session.getLocale())); + bossEventPacket.setTitle(MessageTranslator.convertMessage(title.toString(), session.getLocale())); session.sendUpstreamPacket(bossEventPacket); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandRequestTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandRequestTranslator.java index 1f31367c..f572538e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandRequestTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandRequestTranslator.java @@ -34,7 +34,7 @@ import org.geysermc.connector.network.translators.Translator; import com.github.steveice10.mc.protocol.packet.ingame.client.ClientChatPacket; import com.nukkitx.protocol.bedrock.packet.CommandRequestPacket; -import org.geysermc.connector.utils.MessageUtils; +import org.geysermc.connector.network.translators.chat.MessageTranslator; @Translator(packet = CommandRequestPacket.class) public class BedrockCommandRequestTranslator extends PacketTranslator { @@ -48,7 +48,7 @@ public class BedrockCommandRequestTranslator extends PacketTranslator { @@ -40,7 +40,7 @@ public class BedrockTextTranslator extends PacketTranslator { public void translate(TextPacket packet, GeyserSession session) { String message = packet.getMessage().replaceAll("^\\.", "/").trim(); - if (MessageUtils.isTooLong(message, session)) { + if (MessageTranslator.isTooLong(message, session)) { return; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/chat/MessageTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/chat/MessageTranslator.java new file mode 100644 index 00000000..be01362f --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/chat/MessageTranslator.java @@ -0,0 +1,278 @@ +/* + * Copyright (c) 2019-2020 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.connector.network.translators.chat; + +import com.github.steveice10.mc.protocol.data.game.scoreboard.TeamColor; +import com.github.steveice10.mc.protocol.data.message.style.ChatColor; +import com.github.steveice10.mc.protocol.data.message.style.ChatFormat; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.renderer.TranslatableComponentRenderer; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import net.kyori.adventure.translation.TranslationRegistry; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.utils.LanguageUtils; + +import java.util.*; + +public class MessageTranslator { + + // These are used for handling the translations of the messages + private static final TranslationRegistry REGISTRY = new MinecraftTranslationRegistry(); + private static final TranslatableComponentRenderer RENDERER = TranslatableComponentRenderer.usingTranslationSource(REGISTRY); + + // Store team colors for player names + private static final Map TEAM_COLORS = new HashMap<>(); + + static { + TEAM_COLORS.put(TeamColor.BLACK, getColor(ChatColor.BLACK)); + TEAM_COLORS.put(TeamColor.DARK_BLUE, getColor(ChatColor.DARK_BLUE)); + TEAM_COLORS.put(TeamColor.DARK_GREEN, getColor(ChatColor.DARK_GREEN)); + TEAM_COLORS.put(TeamColor.DARK_AQUA, getColor(ChatColor.DARK_AQUA)); + TEAM_COLORS.put(TeamColor.DARK_RED, getColor(ChatColor.DARK_RED)); + TEAM_COLORS.put(TeamColor.DARK_PURPLE, getColor(ChatColor.DARK_PURPLE)); + TEAM_COLORS.put(TeamColor.GOLD, getColor(ChatColor.GOLD)); + TEAM_COLORS.put(TeamColor.GRAY, getColor(ChatColor.GRAY)); + TEAM_COLORS.put(TeamColor.DARK_GRAY, getColor(ChatColor.DARK_GRAY)); + TEAM_COLORS.put(TeamColor.BLUE, getColor(ChatColor.BLUE)); + TEAM_COLORS.put(TeamColor.GREEN, getColor(ChatColor.GREEN)); + TEAM_COLORS.put(TeamColor.AQUA, getColor(ChatColor.AQUA)); + TEAM_COLORS.put(TeamColor.RED, getColor(ChatColor.RED)); + TEAM_COLORS.put(TeamColor.LIGHT_PURPLE, getColor(ChatColor.LIGHT_PURPLE)); + TEAM_COLORS.put(TeamColor.YELLOW, getColor(ChatColor.YELLOW)); + TEAM_COLORS.put(TeamColor.WHITE, getColor(ChatColor.WHITE)); + TEAM_COLORS.put(TeamColor.OBFUSCATED, getFormat(ChatFormat.OBFUSCATED)); + TEAM_COLORS.put(TeamColor.BOLD, getFormat(ChatFormat.BOLD)); + TEAM_COLORS.put(TeamColor.STRIKETHROUGH, getFormat(ChatFormat.STRIKETHROUGH)); + TEAM_COLORS.put(TeamColor.ITALIC, getFormat(ChatFormat.ITALIC)); + } + + /** + * Convert a Java message to the legacy format ready for bedrock + * + * @param message Java message + * @param locale Locale to use for translation strings + * @return Parsed and formatted message for bedrock + */ + public static String convertMessage(String message, String locale) { + Component component = GsonComponentSerializer.gson().deserialize(message); + + // Get a Locale from the given locale string + Locale localeCode = Locale.forLanguageTag(locale.replace('_', '-')); + component = RENDERER.render(component, localeCode); + + return LegacyComponentSerializer.legacySection().serialize(component); + } + + public static String convertMessage(String message) { + return convertMessage(message, LanguageUtils.getDefaultLocale()); + } + + /** + * Verifies the message is valid JSON in case it's plaintext. Works around GsonComponentSeraializer not using lenient mode. + * See https://wiki.vg/Chat for messages sent in lenient mode, and for a description on leniency. + * + * @param message Potentially lenient JSON message + * @param locale Locale to use for translation strings + * @return Bedrock formatted message + */ + public static String convertMessageLenient(String message, String locale) { + if (isMessage(message)) { + return convertMessage(message, locale); + } else { + String convertedMessage = convertMessage(convertToJavaMessage(message), locale); + + // We have to do this since Adventure strips the starting reset character + if (message.startsWith(getColor(ChatColor.RESET))) { + convertedMessage = getColor(ChatColor.RESET) + convertedMessage; + } + + return convertedMessage; + } + } + + public static String convertMessageLenient(String message) { + return convertMessageLenient(message, LanguageUtils.getDefaultLocale()); + } + + /** + * Convert a Bedrock message string back to a format Java can understand + * + * @param message Message to convert + * @return The formatted JSON string + */ + public static String convertToJavaMessage(String message) { + Component component = LegacyComponentSerializer.legacySection().deserialize(message); + return GsonComponentSerializer.gson().serialize(component); + } + + /** + * Checks if the given text string is a JSON message + * + * @param text String to test + * @return True if its a valid message JSON string, false if not + */ + public static boolean isMessage(String text) { + if (text.trim().isEmpty()) { + return false; + } + + try { + GsonComponentSerializer.gson().deserialize(text); + } catch (Exception ex) { + return false; + } + + return true; + } + + /** + * Convert a {@link ChatColor} into a string for inserting into messages + * + * @param color {@link ChatColor} to convert + * @return The converted color string + */ + private static String getColor(String color) { + String base = "\u00a7"; + switch (color) { + case ChatColor.BLACK: + base += "0"; + break; + case ChatColor.DARK_BLUE: + base += "1"; + break; + case ChatColor.DARK_GREEN: + base += "2"; + break; + case ChatColor.DARK_AQUA: + base += "3"; + break; + case ChatColor.DARK_RED: + base += "4"; + break; + case ChatColor.DARK_PURPLE: + base += "5"; + break; + case ChatColor.GOLD: + base += "6"; + break; + case ChatColor.GRAY: + base += "7"; + break; + case ChatColor.DARK_GRAY: + base += "8"; + break; + case ChatColor.BLUE: + base += "9"; + break; + case ChatColor.GREEN: + base += "a"; + break; + case ChatColor.AQUA: + base += "b"; + break; + case ChatColor.RED: + base += "c"; + break; + case ChatColor.LIGHT_PURPLE: + base += "d"; + break; + case ChatColor.YELLOW: + base += "e"; + break; + case ChatColor.WHITE: + base += "f"; + break; + case ChatColor.RESET: + base += "r"; + break; + default: + return ""; + } + + return base; + } + + /** + * Convert a {@link ChatFormat} into a string for inserting into messages + * + * @param format {@link ChatFormat} to convert + * @return The converted chat formatting string + */ + private static String getFormat(ChatFormat format) { + StringBuilder str = new StringBuilder(); + String base = "\u00a7"; + switch (format) { + case OBFUSCATED: + base += "k"; + break; + case BOLD: + base += "l"; + break; + case STRIKETHROUGH: + base += "m"; + break; + case UNDERLINED: + base += "n"; + break; + case ITALIC: + base += "o"; + break; + default: + return ""; + } + + str.append(base); + + return str.toString(); + } + + /** + * Convert a team color to a chat color + * + * @param teamColor + * @return The chat color character + */ + public static String toChatColor(TeamColor teamColor) { + return TEAM_COLORS.getOrDefault(teamColor, ""); + } + + /** + * Checks if the given message is over 256 characters (Java edition server chat limit) and sends a message to the user if it is + * + * @param message Message to check + * @param session {@link GeyserSession} for the user + * @return True if the message is too long, false if not + */ + public static boolean isTooLong(String message, GeyserSession session) { + if (message.length() > 256) { + session.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.chat.too_long", session.getLocale(), message.length())); + return true; + } + + return false; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/chat/MinecraftTranslationRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/chat/MinecraftTranslationRegistry.java new file mode 100644 index 00000000..a23167ac --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/chat/MinecraftTranslationRegistry.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2019-2020 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.connector.network.translators.chat; + +import net.kyori.adventure.key.Key; +import net.kyori.adventure.translation.TranslationRegistry; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.connector.utils.LocaleUtils; + +import java.text.MessageFormat; +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * This class is used for mapping a translation key with the already loaded Java locale data + * Used in MessageTranslator.java as part of the KyoriPowered/Adventure library + */ +public class MinecraftTranslationRegistry implements TranslationRegistry { + @Override + public @NonNull Key name() { + return Key.key("", ""); + } + + @Override + public @Nullable MessageFormat translate(@NonNull String key, @NonNull Locale locale) { + // Get the locale string + String localeString = LocaleUtils.getLocaleString(key, locale.toString()); + + // Replace the `%s` with numbered inserts `{0}` + Pattern p = Pattern.compile("%s"); + Matcher m = p.matcher(localeString); + StringBuffer sb = new StringBuffer(); + int i = 0; + while (m.find()) { + m.appendReplacement(sb, "{" + (i++) + "}"); + } + m.appendTail(sb); + + return new MessageFormat(sb.toString(), locale); + } + + @Override + public void defaultLocale(@NonNull Locale locale) { + + } + + @Override + public void register(@NonNull String key, @NonNull Locale locale, @NonNull MessageFormat format) { + + } + + @Override + public void unregister(@NonNull String key) { + + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java index 55db9a25..00c9138a 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java @@ -26,7 +26,6 @@ package org.geysermc.connector.network.translators.item; import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; -import com.github.steveice10.mc.protocol.data.message.MessageSerializer; import com.github.steveice10.opennbt.tag.builtin.*; import com.nukkitx.nbt.NbtList; import com.nukkitx.nbt.NbtMap; @@ -44,7 +43,7 @@ import org.geysermc.connector.network.translators.ItemRemapper; import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.utils.FileUtils; import org.geysermc.connector.utils.LanguageUtils; -import org.geysermc.connector.utils.MessageUtils; +import org.geysermc.connector.network.translators.chat.MessageTranslator; import org.reflections.Reflections; import java.util.*; @@ -385,26 +384,17 @@ public abstract class ItemTranslator { public static void translateDisplayProperties(GeyserSession session, CompoundTag tag) { if (tag != null) { CompoundTag display = tag.get("display"); - if (display != null && !display.isEmpty() && display.contains("Name")) { + if (display != null && display.contains("Name")) { String name = ((StringTag) display.get("Name")).getValue(); - // If its not a message convert it - if (!MessageUtils.isMessage(name)) { - Component component = LegacyComponentSerializer.legacySection().deserialize(name); - name = GsonComponentSerializer.gson().serialize(component); - } + // Get the translated name and prefix it with a reset char + name = MessageTranslator.convertMessageLenient(name, session.getLocale()); - // Check if its a message to translate - if (MessageUtils.isMessage(name)) { - // Get the translated name - name = MessageUtils.getTranslatedBedrockMessage(MessageSerializer.fromString(name), session.getLocale()); + // Add the new name tag + display.put(new StringTag("Name", name)); - // Add the new name tag - display.put(new StringTag("Name", name)); - - // Add to the new root tag - tag.put(display); - } + // Add to the new root tag + tag.put(display); } } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BasicItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BasicItemTranslator.java index 1d21bbfb..3fd9df8a 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BasicItemTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BasicItemTranslator.java @@ -37,7 +37,6 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.ItemRemapper; import org.geysermc.connector.network.translators.item.ItemEntry; import org.geysermc.connector.network.translators.item.NbtItemStackTranslator; -import org.geysermc.connector.utils.MessageUtils; import java.util.ArrayList; import java.util.List; @@ -108,7 +107,7 @@ public class BasicItemTranslator extends NbtItemStackTranslator { private String toBedrockMessage(StringTag tag) { String message = tag.getValue(); if (message == null) return null; - TextComponent component = (TextComponent) MessageUtils.phraseJavaMessage(message); + TextComponent component = (TextComponent) GsonComponentSerializer.gson().deserialize(message); String legacy = LegacyComponentSerializer.legacySection().serialize(component); if (hasFormatting(LegacyComponentSerializer.legacySection().deserialize(legacy))) { return "§r" + legacy; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BookPagesTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BookPagesTranslator.java index 41ee4fbc..294dd81e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BookPagesTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BookPagesTranslator.java @@ -33,7 +33,7 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.ItemRemapper; import org.geysermc.connector.network.translators.item.NbtItemStackTranslator; import org.geysermc.connector.network.translators.item.ItemEntry; -import org.geysermc.connector.utils.MessageUtils; +import org.geysermc.connector.network.translators.chat.MessageTranslator; import java.util.ArrayList; import java.util.List; @@ -56,7 +56,7 @@ public class BookPagesTranslator extends NbtItemStackTranslator { CompoundTag pageTag = new CompoundTag(""); pageTag.put(new StringTag("photoname", "")); - pageTag.put(new StringTag("text", MessageUtils.getBedrockMessageLenient(textTag.getValue()))); + pageTag.put(new StringTag("text", MessageTranslator.convertMessageLenient(textTag.getValue()))); pages.add(pageTag); } @@ -78,7 +78,7 @@ public class BookPagesTranslator extends NbtItemStackTranslator { CompoundTag pageTag = (CompoundTag) tag; StringTag textTag = pageTag.get("text"); - pages.add(new StringTag(MessageUtils.getJavaMessage(textTag.getValue()))); + pages.add(new StringTag(MessageTranslator.convertToJavaMessage(textTag.getValue()))); } itemTag.remove("pages"); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaChatTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaChatTranslator.java index 186aaf66..f5128ed6 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaChatTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaChatTranslator.java @@ -25,15 +25,12 @@ package org.geysermc.connector.network.translators.java; -import com.github.steveice10.mc.protocol.data.message.TranslationMessage; import com.github.steveice10.mc.protocol.packet.ingame.server.ServerChatPacket; import com.nukkitx.protocol.bedrock.packet.TextPacket; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.utils.MessageUtils; - -import java.util.List; +import org.geysermc.connector.network.translators.chat.MessageTranslator; @Translator(packet = ServerChatPacket.class) public class JavaChatTranslator extends PacketTranslator { @@ -59,21 +56,8 @@ public class JavaChatTranslator extends PacketTranslator { break; } - String locale = session.getLocale(); - - if (packet.getMessage() instanceof TranslationMessage) { - textPacket.setType(TextPacket.Type.TRANSLATION); - textPacket.setNeedsTranslation(true); - - List paramsTranslated = MessageUtils.getTranslationParams(((TranslationMessage) packet.getMessage()).getWith(), locale, packet.getMessage()); - textPacket.setParameters(paramsTranslated); - - textPacket.setMessage(MessageUtils.insertParams(MessageUtils.getTranslatedBedrockMessage(packet.getMessage(), locale, true, packet.getMessage()), paramsTranslated)); - } else { - textPacket.setNeedsTranslation(false); - - textPacket.setMessage(MessageUtils.getTranslatedBedrockMessage(packet.getMessage(), locale, false, packet.getMessage())); - } + textPacket.setNeedsTranslation(false); + textPacket.setMessage(MessageTranslator.convertMessage(packet.getMessage().toString(), session.getLocale())); session.sendUpstreamPacket(textPacket); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDisconnectPacket.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDisconnectPacket.java index f36da367..1945a8e1 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDisconnectPacket.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDisconnectPacket.java @@ -29,13 +29,13 @@ import com.github.steveice10.mc.protocol.packet.ingame.server.ServerDisconnectPa import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.utils.MessageUtils; +import org.geysermc.connector.network.translators.chat.MessageTranslator; @Translator(packet = ServerDisconnectPacket.class) public class JavaDisconnectPacket extends PacketTranslator { @Override public void translate(ServerDisconnectPacket packet, GeyserSession session) { - session.disconnect(MessageUtils.getTranslatedBedrockMessage(packet.getReason(), session.getLocale(), true)); + session.disconnect(MessageTranslator.convertMessage(packet.getReason().toString(), session.getLocale())); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginDisconnectTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginDisconnectTranslator.java index e7486c99..0a1cc3dd 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginDisconnectTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginDisconnectTranslator.java @@ -29,7 +29,7 @@ import com.github.steveice10.mc.protocol.packet.login.server.LoginDisconnectPack import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.utils.MessageUtils; +import org.geysermc.connector.network.translators.chat.MessageTranslator; @Translator(packet = LoginDisconnectPacket.class) public class JavaLoginDisconnectTranslator extends PacketTranslator { @@ -37,6 +37,6 @@ public class JavaLoginDisconnectTranslator extends PacketTranslator { SetTitlePacket titlePacket = new SetTitlePacket(); String locale = session.getLocale(); + String text; + if (packet.getTitle() == null) { + text = " "; + } else { + text = MessageTranslator.convertMessage(packet.getTitle().toString(), locale); + } + switch (packet.getAction()) { case TITLE: titlePacket.setType(SetTitlePacket.Type.TITLE); - titlePacket.setText(MessageUtils.getTranslatedBedrockMessage(packet.getTitle(), locale)); + titlePacket.setText(text); break; case SUBTITLE: titlePacket.setType(SetTitlePacket.Type.SUBTITLE); - titlePacket.setText(MessageUtils.getTranslatedBedrockMessage(packet.getTitle(), locale)); + titlePacket.setText(text); break; case CLEAR: case RESET: @@ -57,9 +64,10 @@ public class JavaTitleTranslator extends PacketTranslator { break; case ACTION_BAR: titlePacket.setType(SetTitlePacket.Type.ACTIONBAR); - titlePacket.setText(MessageUtils.getTranslatedBedrockMessage(packet.getTitle(), locale)); + titlePacket.setText(text); break; case TIMES: + titlePacket.setType(SetTitlePacket.Type.TIMES); titlePacket.setFadeInTime(packet.getFadeIn()); titlePacket.setFadeOutTime(packet.getFadeOut()); titlePacket.setStayTime(packet.getStay()); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaScoreboardObjectiveTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaScoreboardObjectiveTranslator.java index 31b9d95b..1996f696 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaScoreboardObjectiveTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaScoreboardObjectiveTranslator.java @@ -32,7 +32,7 @@ import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.scoreboard.Objective; import org.geysermc.connector.scoreboard.Scoreboard; import org.geysermc.connector.scoreboard.ScoreboardUpdater; -import org.geysermc.connector.utils.MessageUtils; +import org.geysermc.connector.network.translators.chat.MessageTranslator; import com.github.steveice10.mc.protocol.data.game.scoreboard.ObjectiveAction; import com.github.steveice10.mc.protocol.packet.ingame.server.scoreboard.ServerScoreboardObjectivePacket; @@ -54,7 +54,7 @@ public class JavaScoreboardObjectiveTranslator extends PacketTranslator { switch (packet.getAction()) { case CREATE: scoreboard.registerNewTeam(packet.getTeamName(), toPlayerSet(packet.getPlayers())) - .setName(MessageUtils.getBedrockMessage(packet.getDisplayName())) + .setName(MessageTranslator.convertMessage(packet.getDisplayName().toString())) .setColor(packet.getColor()) .setNameTagVisibility(packet.getNameTagVisibility()) - .setPrefix(MessageUtils.getTranslatedBedrockMessage(packet.getPrefix(), session.getLocale())) - .setSuffix(MessageUtils.getTranslatedBedrockMessage(packet.getSuffix(), session.getLocale())); + .setPrefix(MessageTranslator.convertMessage(packet.getPrefix().toString(), session.getLocale())) + .setSuffix(MessageTranslator.convertMessage(packet.getSuffix().toString(), session.getLocale())); break; case UPDATE: if (team == null) { @@ -74,11 +74,11 @@ public class JavaTeamTranslator extends PacketTranslator { return; } - team.setName(MessageUtils.getBedrockMessage(packet.getDisplayName())) + team.setName(MessageTranslator.convertMessage(packet.getDisplayName().toString())) .setColor(packet.getColor()) .setNameTagVisibility(packet.getNameTagVisibility()) - .setPrefix(MessageUtils.getTranslatedBedrockMessage(packet.getPrefix(), session.getLocale())) - .setSuffix(MessageUtils.getTranslatedBedrockMessage(packet.getSuffix(), session.getLocale())) + .setPrefix(MessageTranslator.convertMessage(packet.getPrefix().toString(), session.getLocale())) + .setSuffix(MessageTranslator.convertMessage(packet.getSuffix().toString(), session.getLocale())) .setUpdateType(UpdateType.UPDATE); break; case ADD_PLAYER: diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java index 2c10ded6..1fb08871 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java @@ -25,7 +25,6 @@ package org.geysermc.connector.network.translators.java.window; -import com.github.steveice10.mc.protocol.data.message.MessageSerializer; import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientCloseWindowPacket; import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerOpenWindowPacket; import org.geysermc.connector.inventory.Inventory; @@ -35,7 +34,7 @@ import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.network.translators.inventory.InventoryTranslator; import org.geysermc.connector.utils.InventoryUtils; import org.geysermc.connector.utils.LocaleUtils; -import org.geysermc.connector.utils.MessageUtils; +import org.geysermc.connector.network.translators.chat.MessageTranslator; @Translator(packet = ServerOpenWindowPacket.class) public class JavaOpenWindowTranslator extends PacketTranslator { @@ -57,8 +56,7 @@ public class JavaOpenWindowTranslator extends PacketTranslator - * The color names correspond to dye names, because of this we can't use {@link MessageUtils#getColor(String)}. + * The color names correspond to dye names, because of this we can't use {@link MessageTranslator#getColor(String)}. * * @param javaColor The dye color stored in the sign's Color tag. * @return A Bedrock Edition formatting code for valid dye colors, otherwise an empty string. */ - private static String getBedrockSignColor(String javaColor) { + private String getBedrockSignColor(String javaColor) { String base = "\u00a7"; switch (javaColor) { case "white": @@ -100,7 +99,12 @@ public class SignBlockEntityTranslator extends BlockEntityTranslator { for (int i = 0; i < 4; i++) { int currentLine = i + 1; String signLine = getOrDefault(tag.getValue().get("Text" + currentLine), ""); - signLine = MessageUtils.getBedrockMessage(MessageSerializer.fromString(signLine)); + signLine = MessageTranslator.convertMessageLenient(signLine); + + // Trim any trailing formatting codes + if (signLine.length() > 2 && signLine.toCharArray()[signLine.length() - 2] == '\u00a7') { + signLine = signLine.substring(0, signLine.length() - 2); + } // Check the character width on the sign to ensure there is no overflow that is usually hidden // to Java Edition clients but will appear to Bedrock clients @@ -124,6 +128,6 @@ public class SignBlockEntityTranslator extends BlockEntityTranslator { signText.append("\n"); } - builder.put("Text", MessageUtils.getBedrockMessage(MessageSerializer.fromString(signText.toString()))); + builder.put("Text", signText.toString()); } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java b/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java index 63255cfa..0b2b132a 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java @@ -159,7 +159,8 @@ public class FileUtils { } /** - * Calculate the SHA256 hash of the resource pack file + * Calculate the SHA256 hash of a file + * * @param file File to calculate the hash for * @return A byte[] representation of the hash */ @@ -175,6 +176,24 @@ public class FileUtils { return sha256; } + /** + * Calculate the SHA1 hash of a file + * + * @param file File to calculate the hash for + * @return A byte[] representation of the hash + */ + public static byte[] calculateSHA1(File file) { + byte[] sha1; + + try { + sha1 = MessageDigest.getInstance("SHA-1").digest(Files.readAllBytes(file.toPath())); + } catch (Exception e) { + throw new RuntimeException("Could not calculate pack hash", e); + } + + return sha1; + } + /** * Get the stored reflection data for a given path * diff --git a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java index dfde21b3..4e9e4b00 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java @@ -47,7 +47,7 @@ public class LocaleUtils { private static final Map ASSET_MAP = new HashMap<>(); - private static String smallestURL = ""; + private static VersionDownload clientJarInfo; static { // Create the locales folder @@ -87,9 +87,8 @@ public class LocaleUtils { // Get the client jar for use when downloading the en_us locale GeyserConnector.getInstance().getLogger().debug(GeyserConnector.JSON_MAPPER.writeValueAsString(versionInfo.getDownloads())); - VersionDownload download = versionInfo.getDownloads().get("client"); - GeyserConnector.getInstance().getLogger().debug(GeyserConnector.JSON_MAPPER.writeValueAsString(download)); - smallestURL = download.getUrl(); + clientJarInfo = versionInfo.getDownloads().get("client"); + GeyserConnector.getInstance().getLogger().debug(GeyserConnector.JSON_MAPPER.writeValueAsString(clientJarInfo)); // Get the assets list JsonNode assets = GeyserConnector.JSON_MAPPER.readTree(WebUtils.getBody(versionInfo.getAssetIndex().getUrl())).get("objects"); @@ -136,8 +135,28 @@ public class LocaleUtils { // Check if we have already downloaded the locale file if (localeFile.exists()) { - GeyserConnector.getInstance().getLogger().debug("Locale already downloaded: " + locale); - return; + String curHash = ""; + String targetHash = ""; + + if (locale.equals("en_us")) { + try { + Path hashFile = localeFile.getParentFile().toPath().resolve("en_us.hash"); + if (hashFile.toFile().exists()) { + curHash = String.join("", Files.readAllLines(hashFile)); + } + } catch (IOException ignored) { } + targetHash = clientJarInfo.getSha1(); + } else { + curHash = byteArrayToHexString(FileUtils.calculateSHA1(localeFile)); + targetHash = ASSET_MAP.get("minecraft/lang/" + locale + ".json").getHash(); + } + + if (!curHash.equals(targetHash)) { + GeyserConnector.getInstance().getLogger().debug("Locale out of date; re-downloading: " + locale); + } else { + GeyserConnector.getInstance().getLogger().debug("Locale already downloaded and up-to date: " + locale); + return; + } } // Create the en_us locale @@ -202,11 +221,11 @@ public class LocaleUtils { try { // Let the user know we are downloading the JAR GeyserConnector.getInstance().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.locale.download.en_us")); - GeyserConnector.getInstance().getLogger().debug("Download URL: " + smallestURL); + GeyserConnector.getInstance().getLogger().debug("Download URL: " + clientJarInfo.getUrl()); // Download the smallest JAR (client or server) Path tmpFilePath = GeyserConnector.getInstance().getBootstrap().getConfigFolder().resolve("tmp_locale.jar"); - WebUtils.downloadFile(smallestURL, tmpFilePath.toString()); + WebUtils.downloadFile(clientJarInfo.getUrl(), tmpFilePath.toString()); // Load in the JAR as a zip and extract the file ZipFile localeJar = new ZipFile(tmpFilePath.toString()); @@ -227,6 +246,9 @@ public class LocaleUtils { fileStream.close(); localeJar.close(); + // Store the latest jar hash + FileUtils.writeFile(localeFile.getParentFile().toPath().resolve("en_us.hash").toString(), clientJarInfo.getSha1().toCharArray()); + // Delete the nolonger needed client/server jar Files.delete(tmpFilePath); } catch (Exception e) { @@ -255,6 +277,20 @@ public class LocaleUtils { return localeStrings.getOrDefault(messageText, messageText); } + /** + * Convert a byte array into a hex string + * + * @param b Byte array to convert + * @return The hex representation of the given byte array + */ + private static String byteArrayToHexString(byte[] b) { + StringBuilder result = new StringBuilder(); + for (byte value : b) { + result.append(Integer.toString((value & 0xff) + 0x100, 16).substring(1)); + } + return result.toString(); + } + public static void init() { // no-op } diff --git a/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java b/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java deleted file mode 100644 index b5a2bfdc..00000000 --- a/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java +++ /dev/null @@ -1,494 +0,0 @@ -/* - * Copyright (c) 2019-2020 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.connector.utils; - -import com.github.steveice10.mc.protocol.data.game.scoreboard.TeamColor; -import com.github.steveice10.mc.protocol.data.message.Message; -import com.github.steveice10.mc.protocol.data.message.MessageSerializer; -import com.github.steveice10.mc.protocol.data.message.TextMessage; -import com.github.steveice10.mc.protocol.data.message.TranslationMessage; -import com.github.steveice10.mc.protocol.data.message.style.ChatColor; -import com.github.steveice10.mc.protocol.data.message.style.ChatFormat; -import com.github.steveice10.mc.protocol.data.message.style.MessageStyle; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; -import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; -import org.geysermc.connector.network.session.GeyserSession; - -import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class MessageUtils { - - private static final Map COLORS = new HashMap<>(); - private static final Map TEAM_COLORS = new HashMap<>(); - - static { - COLORS.put(ChatColor.BLACK, 0x000000); - COLORS.put(ChatColor.DARK_BLUE, 0x0000aa); - COLORS.put(ChatColor.DARK_GREEN, 0x00aa00); - COLORS.put(ChatColor.DARK_AQUA, 0x00aaaa); - COLORS.put(ChatColor.DARK_RED, 0xaa0000); - COLORS.put(ChatColor.DARK_PURPLE, 0xaa00aa); - COLORS.put(ChatColor.GOLD, 0xffaa00); - COLORS.put(ChatColor.GRAY, 0xaaaaaa); - COLORS.put(ChatColor.DARK_GRAY, 0x555555); - COLORS.put(ChatColor.BLUE, 0x5555ff); - COLORS.put(ChatColor.GREEN, 0x55ff55); - COLORS.put(ChatColor.AQUA, 0x55ffff); - COLORS.put(ChatColor.RED, 0xff5555); - COLORS.put(ChatColor.LIGHT_PURPLE, 0xff55ff); - COLORS.put(ChatColor.YELLOW, 0xffff55); - COLORS.put(ChatColor.WHITE, 0xffffff); - - TEAM_COLORS.put(TeamColor.BLACK, getColor(ChatColor.BLACK)); - TEAM_COLORS.put(TeamColor.DARK_BLUE, getColor(ChatColor.DARK_BLUE)); - TEAM_COLORS.put(TeamColor.DARK_GREEN, getColor(ChatColor.DARK_GREEN)); - TEAM_COLORS.put(TeamColor.DARK_AQUA, getColor(ChatColor.DARK_AQUA)); - TEAM_COLORS.put(TeamColor.DARK_RED, getColor(ChatColor.DARK_RED)); - TEAM_COLORS.put(TeamColor.DARK_PURPLE, getColor(ChatColor.DARK_PURPLE)); - TEAM_COLORS.put(TeamColor.GOLD, getColor(ChatColor.GOLD)); - TEAM_COLORS.put(TeamColor.GRAY, getColor(ChatColor.GRAY)); - TEAM_COLORS.put(TeamColor.DARK_GRAY, getColor(ChatColor.DARK_GRAY)); - TEAM_COLORS.put(TeamColor.BLUE, getColor(ChatColor.BLUE)); - TEAM_COLORS.put(TeamColor.GREEN, getColor(ChatColor.GREEN)); - TEAM_COLORS.put(TeamColor.AQUA, getColor(ChatColor.AQUA)); - TEAM_COLORS.put(TeamColor.RED, getColor(ChatColor.RED)); - TEAM_COLORS.put(TeamColor.LIGHT_PURPLE, getColor(ChatColor.LIGHT_PURPLE)); - TEAM_COLORS.put(TeamColor.YELLOW, getColor(ChatColor.YELLOW)); - TEAM_COLORS.put(TeamColor.WHITE, getColor(ChatColor.WHITE)); - TEAM_COLORS.put(TeamColor.OBFUSCATED, getFormat(Collections.singletonList(ChatFormat.OBFUSCATED))); - TEAM_COLORS.put(TeamColor.BOLD, getFormat(Collections.singletonList(ChatFormat.BOLD))); - TEAM_COLORS.put(TeamColor.STRIKETHROUGH, getFormat(Collections.singletonList(ChatFormat.STRIKETHROUGH))); - TEAM_COLORS.put(TeamColor.ITALIC, getFormat(Collections.singletonList(ChatFormat.ITALIC))); - } - - /** - * Recursively parse each message from a list for usage in a {@link TranslationMessage} - * - * @param messages A {@link List} of {@link Message} to parse - * @param locale A locale loaded to get the message for - * @param parent A {@link Message} to use as the parent (can be null) - * @return the translation parameters - */ - public static List getTranslationParams(List messages, String locale, Message parent) { - List strings = new ArrayList<>(); - for (Message message : messages) { - message = fixMessageStyle(message, parent); - - if (message instanceof TranslationMessage) { - TranslationMessage translation = (TranslationMessage) message; - - if (locale == null) { - String builder = "%" + translation.getKey(); - strings.add(builder); - } - - // Collect all params and add format corrections to the end of them - List furtherParams = new ArrayList<>(); - for (String param : getTranslationParams(translation.getWith(), locale, message)) { - String newParam = param; - if (parent.getStyle().getFormats().size() != 0) { - newParam += getFormat(parent.getStyle().getFormats()); - } - if (parent.getStyle().getColor() != ChatColor.NONE) { - newParam += getColor(parent.getStyle().getColor()); - } - - furtherParams.add(newParam); - } - - if (locale != null) { - String builder = getFormat(message.getStyle().getFormats()) + - getColor(message.getStyle().getColor()); - builder += insertParams(LocaleUtils.getLocaleString(translation.getKey(), locale), furtherParams); - strings.add(builder); - } else { - String format = getFormat(message.getStyle().getFormats()) + - getColor(message.getStyle().getColor()); - for (String param : furtherParams) { - strings.add(format + param); - } - } - } else { - String builder = getFormat(message.getStyle().getFormats()) + - getColor(message.getStyle().getColor()); - builder += getTranslatedBedrockMessage(message, locale, false, parent); - strings.add(builder); - } - } - - return strings; - } - - public static String getTranslatedBedrockMessage(Message message, String locale) { - return getTranslatedBedrockMessage(message, locale, true); - } - - public static String getTranslatedBedrockMessage(Message message, String locale, boolean shouldTranslate) { - return getTranslatedBedrockMessage(message, locale, shouldTranslate, null); - } - - /** - * Translate a given {@link TranslationMessage} to the given locale - * - * @param message The {@link Message} to send - * @param locale the locale - * @param shouldTranslate if the message should be translated - * @param parent the parent message - * @return the given translation message translated from the given locale - */ - public static String getTranslatedBedrockMessage(Message message, String locale, boolean shouldTranslate, Message parent) { - JsonParser parser = new JsonParser(); - if (isMessage(message.toString())) { - JsonObject object = parser.parse(message.toString()).getAsJsonObject(); - message = MessageSerializer.fromJson(object); - } - - message = fixMessageStyle(message, parent); - - String messageText = (message instanceof TranslationMessage) ? ((TranslationMessage) message).getKey() : ((TextMessage) message).getText(); - if (locale != null && shouldTranslate) { - messageText = LocaleUtils.getLocaleString(messageText, locale); - } - - StringBuilder builder = new StringBuilder(); - builder.append(getFormat(message.getStyle().getFormats())); - builder.append(getColor(message.getStyle().getColor())); - builder.append(messageText); - - for (Message msg : message.getExtra()) { - builder.append(getFormat(msg.getStyle().getFormats())); - builder.append(getColor(msg.getStyle().getColor())); - if (!(msg.toString() == null)) { - boolean isTranslationMessage = (msg instanceof TranslationMessage); - String extraText = ""; - - if (isTranslationMessage) { - List paramsTranslated = getTranslationParams(((TranslationMessage) msg).getWith(), locale, message); - extraText = insertParams(getTranslatedBedrockMessage(msg, locale, isTranslationMessage, message), paramsTranslated); - } else { - extraText = getTranslatedBedrockMessage(msg, locale, isTranslationMessage, message); - } - - builder.append(extraText); - builder.append("\u00a7r"); - } - } - - return builder.toString(); - } - - /** - * If the passed {@link Message} color or format are empty then copy from parent - * - * @param message {@link Message} to update - * @param parent Parent {@link Message} for style - * @return The updated {@link Message} - */ - private static Message fixMessageStyle(Message message, Message parent) { - if (parent == null) { - return message; - } - MessageStyle.Builder styleBuilder = message.getStyle().toBuilder(); - - // Copy color from parent - if (message.getStyle().getColor() == ChatColor.NONE) { - styleBuilder.color(parent.getStyle().getColor()); - } - - // Copy formatting from parent - if (message.getStyle().getFormats().size() == 0) { - styleBuilder.formats(parent.getStyle().getFormats()); - } - - return message.toBuilder().style(styleBuilder.build()).build(); - } - - public static String getBedrockMessage(Message message) { - if (isMessage(((TextMessage) message).getText())) { - return getBedrockMessage(((TextMessage) message).getText()); - } else { - return getBedrockMessage(MessageSerializer.toJsonString(message)); - } - } - - /** - * Verifies the message is valid JSON in case it's plaintext. Works around GsonComponentSeraializer not using lenient mode. - * See https://wiki.vg/Chat for messages sent in lenient mode, and for a description on leniency. - * - * @param message Potentially lenient JSON message - * @return Bedrock formatted message - */ - public static String getBedrockMessageLenient(String message) { - if (isMessage(message)) { - return getBedrockMessage(message); - } else { - final JsonObject obj = new JsonObject(); - obj.addProperty("text", message); - return getBedrockMessage(obj.toString()); - } - } - - public static String getBedrockMessage(String message) { - Component component = phraseJavaMessage(message); - return LegacyComponentSerializer.legacySection().serialize(component); - } - - public static Component phraseJavaMessage(String message) { - return GsonComponentSerializer.gson().deserialize(message); - } - - public static String getJavaMessage(String message) { - Component component = LegacyComponentSerializer.legacySection().deserialize(message); - return GsonComponentSerializer.gson().serialize(component); - } - - /** - * Inserts the given parameters into the given message both in sequence and as requested - * - * @param message Message containing possible parameter replacement strings - * @param params A list of parameter strings - * @return Parsed message with all params inserted as needed - */ - public static String insertParams(String message, List params) { - String newMessage = message; - - Pattern p = Pattern.compile("%([1-9])\\$s"); - Matcher m = p.matcher(message); - while (m.find()) { - try { - newMessage = newMessage.replaceFirst("%" + m.group(1) + "\\$s", params.get(Integer.parseInt(m.group(1)) - 1)); - } catch (Exception e) { - // Couldn't find the param to replace - } - } - - for (String text : params) { - newMessage = newMessage.replaceFirst("%s", text.replaceAll("%s", "%r")); - } - - newMessage = newMessage.replaceAll("%r", "MISSING!"); - - return newMessage; - } - - /** - * Convert a ChatColor into a string for inserting into messages - * - * @param color ChatColor to convert - * @return The converted color string - */ - private static String getColor(String color) { - String base = "\u00a7"; - switch (color) { - case ChatColor.BLACK: - base += "0"; - break; - case ChatColor.DARK_BLUE: - base += "1"; - break; - case ChatColor.DARK_GREEN: - base += "2"; - break; - case ChatColor.DARK_AQUA: - base += "3"; - break; - case ChatColor.DARK_RED: - base += "4"; - break; - case ChatColor.DARK_PURPLE: - base += "5"; - break; - case ChatColor.GOLD: - base += "6"; - break; - case ChatColor.GRAY: - base += "7"; - break; - case ChatColor.DARK_GRAY: - base += "8"; - break; - case ChatColor.BLUE: - base += "9"; - break; - case ChatColor.GREEN: - base += "a"; - break; - case ChatColor.AQUA: - base += "b"; - break; - case ChatColor.RED: - base += "c"; - break; - case ChatColor.LIGHT_PURPLE: - base += "d"; - break; - case ChatColor.YELLOW: - base += "e"; - break; - case ChatColor.WHITE: - base += "f"; - break; - case ChatColor.RESET: - //case NONE: - base += "r"; - break; - case "": // To stop recursion - return ""; - default: - return getClosestColor(color); - } - - return base; - } - - /** - * Based on https://github.com/ViaVersion/ViaBackwards/blob/master/core/src/main/java/nl/matsv/viabackwards/protocol/protocol1_15_2to1_16/chat/TranslatableRewriter1_16.java - * - * @param color A color string - * @return The closest color to that string - */ - private static String getClosestColor(String color) { - if (!color.startsWith("#")) { - return ""; - } - - int rgb = Integer.parseInt(color.substring(1), 16); - int r = (rgb >> 16) & 0xFF; - int g = (rgb >> 8) & 0xFF; - int b = rgb & 0xFF; - - String closest = null; - int smallestDiff = 0; - - for (Map.Entry testColor : COLORS.entrySet()) { - if (testColor.getValue() == rgb) { - closest = testColor.getKey(); - break; - } - - int testR = (testColor.getValue() >> 16) & 0xFF; - int testG = (testColor.getValue() >> 8) & 0xFF; - int testB = testColor.getValue() & 0xFF; - - // Check by the greatest diff of the 3 values - int rAverage = (testR + r) / 2; - int rDiff = testR - r; - int gDiff = testG - g; - int bDiff = testB - b; - int diff = ((2 + (rAverage >> 8)) * rDiff * rDiff) - + (4 * gDiff * gDiff) - + ((2 + ((255 - rAverage) >> 8)) * bDiff * bDiff); - if (closest == null || diff < smallestDiff) { - closest = testColor.getKey(); - smallestDiff = diff; - } - } - - return getColor(closest); - } - - /** - * Convert a list of ChatFormats into a string for inserting into messages - * - * @param formats ChatFormats to convert - * @return The converted chat formatting string - */ - private static String getFormat(List formats) { - StringBuilder str = new StringBuilder(); - for (ChatFormat cf : formats) { - String base = "\u00a7"; - switch (cf) { - case OBFUSCATED: - base += "k"; - break; - case BOLD: - base += "l"; - break; - case STRIKETHROUGH: - base += "m"; - break; - case UNDERLINED: - base += "n"; - break; - case ITALIC: - base += "o"; - break; - default: - return ""; - } - - str.append(base); - } - - return str.toString(); - } - - /** - * Checks if the given text string is a json message - * - * @param text String to test - * @return True if its a valid message json string, false if not - */ - public static boolean isMessage(String text) { - JsonParser parser = new JsonParser(); - try { - JsonObject object = parser.parse(text).getAsJsonObject(); - try { - MessageSerializer.fromJson(object); - } catch (Exception ex) { - return false; - } - } catch (Exception ex) { - return false; - } - return true; - } - - public static String toChatColor(TeamColor teamColor) { - return TEAM_COLORS.getOrDefault(teamColor, ""); - } - - /** - * Checks if the given message is over 256 characters (Java edition server chat limit) and sends a message to the user if it is - * - * @param message Message to check - * @param session GeyserSession for the user - * @return True if the message is too long, false if not - */ - public static boolean isTooLong(String message, GeyserSession session) { - if (message.length() > 256) { - session.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.chat.too_long", session.getLocale(), message.length())); - return true; - } - - return false; - } -} diff --git a/connector/src/test/java/org/geysermc/connector/network/translators/chat/MessageTranslatorTest.java b/connector/src/test/java/org/geysermc/connector/network/translators/chat/MessageTranslatorTest.java new file mode 100644 index 00000000..5d52c79b --- /dev/null +++ b/connector/src/test/java/org/geysermc/connector/network/translators/chat/MessageTranslatorTest.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2019-2020 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.connector.network.translators.chat; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +public class MessageTranslatorTest { + + private Map messages = new HashMap<>(); + + @Before + public void setUp() throws Exception { + messages.put("{\"text\":\"\",\"extra\":[{\"text\":\"DoctorMad9952 joined the game\",\"color\":\"yellow\"}]}", + "§eDoctorMad9952 joined the game"); + + messages.put("{\"text\":\"\",\"extra\":[\"Plugins (3): \",{\"text\":\"WorldEdit\",\"color\":\"green\"},{\"text\":\", \",\"color\":\"white\"},{\"text\":\"ViaVersion\",\"color\":\"green\"},{\"text\":\", \",\"color\":\"white\"},{\"text\":\"Geyser-Spigot\",\"color\":\"green\"}]}", + "Plugins (3): §aWorldEdit§f, §aViaVersion§f, §aGeyser-Spigot"); + + // RGB downgrade test + messages.put("{\"extra\":[{\"text\":\" \"},{\"color\":\"gold\",\"text\":\"The \"},{\"color\":\"#E14248\",\"obfuscated\":true,\"text\":\"||\"},{\"color\":\"#3AA9FF\",\"bold\":true,\"text\":\"CubeCraft\"},{\"color\":\"#E14248\",\"obfuscated\":true,\"text\":\"||\"},{\"color\":\"gold\",\"text\":\" Network \"},{\"color\":\"green\",\"text\":\"[1.8/1.9+]\\n \"},{\"color\":\"#f5e342\",\"text\":\"✦ \"},{\"color\":\"#b042f5\",\"bold\":true,\"text\":\"N\"},{\"color\":\"#c142f5\",\"bold\":true,\"text\":\"E\"},{\"color\":\"#d342f5\",\"bold\":true,\"text\":\"W\"},{\"color\":\"#e442f5\",\"bold\":true,\"text\":\":\"},{\"color\":\"#f542f5\",\"bold\":true,\"text\":\" \"},{\"color\":\"#bcf542\",\"bold\":true,\"text\":\"A\"},{\"color\":\"#acee3f\",\"bold\":true,\"text\":\"M\"},{\"color\":\"#9ce73c\",\"bold\":true,\"text\":\"O\"},{\"color\":\"#8ce039\",\"bold\":true,\"text\":\"N\"},{\"color\":\"#7cd936\",\"bold\":true,\"text\":\"G\"},{\"color\":\"#6cd233\",\"bold\":true,\"text\":\" \"},{\"color\":\"#5ccb30\",\"bold\":true,\"text\":\"S\"},{\"color\":\"#4cc42d\",\"bold\":true,\"text\":\"L\"},{\"color\":\"#3cbd2a\",\"bold\":true,\"text\":\"I\"},{\"color\":\"#2cb627\",\"bold\":true,\"text\":\"M\"},{\"color\":\"#1caf24\",\"bold\":true,\"text\":\"E\"},{\"color\":\"#0ca821\",\"bold\":true,\"text\":\"S\"},{\"color\":\"#f5e342\",\"text\":\" \"},{\"color\":\"#6d7c87\",\"text\":\"(kinda sus) \"},{\"color\":\"#f5e342\",\"text\":\"✦\"}],\"text\":\"\"}", + " §6The §c§k||§r§3§lCubeCraft§r§c§k||§r§6 Network §a[1.8/1.9+]\n" + + " §e✦ §d§lN§r§d§lE§r§d§lW§r§d§l:§r§d§l §r§e§lA§r§e§lM§r§a§lO§r§a§lN§r§a§lG§r§a§l §r§a§lS§r§a§lL§r§2§lI§r§2§lM§r§2§lE§r§2§lS§r§e §8(kinda sus) §e✦"); + } + + @Test + public void convertMessage() { + for (Map.Entry entry : messages.entrySet()) { + String bedrockMessage = MessageTranslator.convertMessage(entry.getKey(), "en_US"); + Assert.assertEquals("Translation of messages is incorrect", bedrockMessage, entry.getValue()); + } + } + + @Test + public void convertMessageLenient() { + Assert.assertEquals("All newline message is not handled properly", "\n\n\n\n", MessageTranslator.convertMessageLenient("\n\n\n\n")); + Assert.assertEquals("Empty message is not handled properly", "", MessageTranslator.convertMessageLenient("")); + Assert.assertEquals("Reset before message is not handled properly", "§r§eGame Selector", MessageTranslator.convertMessageLenient("§r§eGame Selector")); + } +} From 123b074cc76785135e9b374706791efb1cc41bb9 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Tue, 17 Nov 2020 12:03:12 -0500 Subject: [PATCH 042/116] Update to Bedrock 1.16.100 (#1552) * Initial work on 1.16.100 - currently crashes the client * Update runtime item states * Use new Bedrock runtime IDs Bedrock now hardcodes block runtime IDs in alphabetical order of the identifiers. This commit updates Geyser to accomodate. - Remove runtime_block_states.dat and replace it with blockpalette.nbt - Calculate the block runtime ID based on the order of the block palette - Separate BlockTranslator.AIR into Bedrock and Java values - Update the second layer of chunks to use air when not waterlogged - Don't send item palette for now, as that's what crashes the game (will look into for v415) - Other misc. changes * Improve second layer chunk translation * v415 support - Add a message warning people they are on a beta version of Geyser * Update to protocol v417 There are still some mappings changes that need to be gone through. * Update runtime item states and clean up item frames * Future-proof enchanment table * Update for v418 * Update to v419 * Apply proper air ID to waterlogged chunk layer * Fix missing import * Remove beta warning * Update mappings * Manually patch runtime_item_states and send the ITEMS registry * Update README * Disable grindstone and smithing inventories (since they're broken) * Use artifactory jenkins plugin (#1548) * Use artifactory jenkins plugin * Bump version to 1.2.0-SNAPSHOT Co-authored-by: SupremeMortal <6178101+SupremeMortal@users.noreply.github.com> --- Jenkinsfile | 23 +- README.md | 2 +- bootstrap/bungeecord/pom.xml | 6 +- bootstrap/pom.xml | 5 +- bootstrap/spigot/pom.xml | 6 +- .../world/GeyserSpigotWorldManager.java | 6 +- bootstrap/sponge/pom.xml | 6 +- bootstrap/standalone/pom.xml | 6 +- bootstrap/velocity/pom.xml | 6 +- common/pom.xml | 5 +- connector/pom.xml | 11 +- .../connector/entity/ItemFrameEntity.java | 10 +- .../connector/network/BedrockProtocol.java | 6 +- .../network/session/GeyserSession.java | 2 - .../network/session/cache/ChunkCache.java | 6 +- .../BedrockBlockPickRequestTranslator.java | 2 +- ...BedrockInventoryTransactionTranslator.java | 10 +- .../EnchantmentInventoryTranslator.java | 9 +- .../inventory/InventoryTranslator.java | 4 +- .../translators/item/ItemRegistry.java | 45 +- .../translators/nbt/CrossbowTranslator.java | 2 +- .../nbt/ShulkerBoxItemTranslator.java | 2 +- .../java/world/JavaExplosionTranslator.java | 2 +- .../translators/world/WorldManager.java | 10 +- .../world/block/BlockTranslator.java | 85 +- .../translators/world/chunk/BlockStorage.java | 3 +- .../geysermc/connector/utils/ChunkUtils.java | 23 +- .../main/resources/bedrock/blockpalette.nbt | Bin 0 -> 1160789 bytes .../resources/bedrock/creative_items.json | 3503 +++++----- .../resources/bedrock/legacy_block_ids.json | 555 -- .../resources/bedrock/legacy_item_ids.json | 255 - .../bedrock/runtime_block_states.dat | Bin 1063028 -> 0 bytes .../{items.json => runtime_item_states.json} | 5852 +++++++++-------- connector/src/main/resources/mappings | 2 +- pom.xml | 15 +- 35 files changed, 5019 insertions(+), 5466 deletions(-) create mode 100644 connector/src/main/resources/bedrock/blockpalette.nbt delete mode 100644 connector/src/main/resources/bedrock/legacy_block_ids.json delete mode 100644 connector/src/main/resources/bedrock/legacy_item_ids.json delete mode 100644 connector/src/main/resources/bedrock/runtime_block_states.dat rename connector/src/main/resources/bedrock/{items.json => runtime_item_states.json} (84%) diff --git a/Jenkinsfile b/Jenkinsfile index 1a93391d..96b189f2 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -24,8 +24,29 @@ pipeline { when { branch "master" } + steps { - sh 'mvn javadoc:jar source:jar deploy -DskipTests' + rtMavenDeployer( + id: "maven-deployer", + serverId: "opencollab-artifactory", + releaseRepo: "maven-releases", + snapshotRepo: "maven-snapshots" + ) + rtMavenResolver( + id: "maven-resolver", + serverId: "opencollab-artifactory", + releaseRepo: "release", + snapshotRepo: "snapshot" + ) + rtMavenRun( + pom: 'pom.xml', + goals: 'javadoc:jar source:jar install -DskipTests', + deployerId: "maven-deployer", + resolverId: "maven-resolver" + ) + rtPublishBuildInfo( + serverId: "opencollab-artifactory" + ) } } } diff --git a/README.md b/README.md index e2efc5b4..ab1ff24a 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,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 now joined us here! -### Currently supporting Minecraft Bedrock v1.16.x and Minecraft Java v1.16.4. +### Currently supporting Minecraft Bedrock v1.16.100 and Minecraft Java v1.16.4. ## Setting Up Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set up Geyser. diff --git a/bootstrap/bungeecord/pom.xml b/bootstrap/bungeecord/pom.xml index 44b28e93..124967b0 100644 --- a/bootstrap/bungeecord/pom.xml +++ b/bootstrap/bungeecord/pom.xml @@ -6,15 +6,15 @@ org.geysermc bootstrap-parent - 1.1.0 - ../ + 1.2.0-SNAPSHOT bootstrap-bungeecord + org.geysermc connector - 1.1.0 + 1.2.0-SNAPSHOT compile diff --git a/bootstrap/pom.xml b/bootstrap/pom.xml index d9bac67d..a5ad53cb 100644 --- a/bootstrap/pom.xml +++ b/bootstrap/pom.xml @@ -6,12 +6,11 @@ org.geysermc geyser-parent - parent - ../ + 1.2.0-SNAPSHOT bootstrap-parent - 1.1.0 pom + spigot-public diff --git a/bootstrap/spigot/pom.xml b/bootstrap/spigot/pom.xml index 067b446a..f3b9cf88 100644 --- a/bootstrap/spigot/pom.xml +++ b/bootstrap/spigot/pom.xml @@ -6,15 +6,15 @@ org.geysermc bootstrap-parent - 1.1.0 - ../ + 1.2.0-SNAPSHOT bootstrap-spigot + org.geysermc connector - 1.1.0 + 1.2.0-SNAPSHOT compile diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java index 28b2da3a..3493fc25 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java @@ -119,7 +119,7 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { if ((this.isLegacy && !this.isViaVersion) || session.getPlayerEntity() == null || (bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) { - return BlockTranslator.AIR; + return BlockTranslator.JAVA_AIR_ID; } World world = bukkitPlayer.getWorld(); if (isLegacy) { @@ -136,7 +136,7 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { BlockStorage storage = Via.getManager().getConnection(bukkitPlayer.getUniqueId()).get(BlockStorage.class); return getLegacyBlock(storage, bukkitPlayer.getWorld(), x, y, z); } else { - return BlockTranslator.AIR; + return BlockTranslator.JAVA_AIR_ID; } } @@ -190,7 +190,7 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { for (int blockZ = 0; blockZ < 16; blockZ++) { for (int blockX = 0; blockX < 16; blockX++) { Block block = world.getBlockAt((x << 4) + blockX, (y << 4) + blockY, (z << 4) + blockZ); - int id = BlockTranslator.getJavaIdBlockMap().getOrDefault(block.getBlockData().getAsString(), BlockTranslator.AIR); + int id = BlockTranslator.getJavaIdBlockMap().getOrDefault(block.getBlockData().getAsString(), BlockTranslator.JAVA_AIR_ID); chunk.set(blockX, blockY, blockZ, id); } } diff --git a/bootstrap/sponge/pom.xml b/bootstrap/sponge/pom.xml index 132f3817..e6ce8f85 100644 --- a/bootstrap/sponge/pom.xml +++ b/bootstrap/sponge/pom.xml @@ -6,15 +6,15 @@ org.geysermc bootstrap-parent - 1.1.0 - ../ + 1.2.0-SNAPSHOT bootstrap-sponge + org.geysermc connector - 1.1.0 + 1.2.0-SNAPSHOT compile diff --git a/bootstrap/standalone/pom.xml b/bootstrap/standalone/pom.xml index 07458f73..831239f6 100644 --- a/bootstrap/standalone/pom.xml +++ b/bootstrap/standalone/pom.xml @@ -6,15 +6,15 @@ org.geysermc bootstrap-parent - 1.1.0 - ../ + 1.2.0-SNAPSHOT bootstrap-standalone + org.geysermc connector - 1.1.0 + 1.2.0-SNAPSHOT compile diff --git a/bootstrap/velocity/pom.xml b/bootstrap/velocity/pom.xml index babb9a3e..86de99ba 100644 --- a/bootstrap/velocity/pom.xml +++ b/bootstrap/velocity/pom.xml @@ -6,15 +6,15 @@ org.geysermc bootstrap-parent - 1.1.0 - ../ + 1.2.0-SNAPSHOT bootstrap-velocity + org.geysermc connector - 1.1.0 + 1.2.0-SNAPSHOT compile diff --git a/common/pom.xml b/common/pom.xml index 85dde12c..32c4b187 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -6,11 +6,10 @@ org.geysermc geyser-parent - parent - ../ + 1.2.0-SNAPSHOT common - 1.1.0 + com.google.code.gson diff --git a/connector/pom.xml b/connector/pom.xml index db267a71..7c44ddfd 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -6,16 +6,15 @@ org.geysermc geyser-parent - parent - ../ + 1.2.0-SNAPSHOT connector - 1.1.0 + org.geysermc common - 1.1.0 + 1.2.0-SNAPSHOT compile @@ -32,8 +31,8 @@ com.github.CloudburstMC.Protocol - bedrock-v408 - 02f46a8700 + bedrock-v419 + ce59d39118 compile diff --git a/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java b/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java index 501c7e46..62fe3afe 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java @@ -69,7 +69,6 @@ public class ItemFrameEntity extends Entity { public ItemFrameEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation, HangingDirection direction) { super(entityId, geyserId, entityType, position, motion, rotation); - NbtMapBuilder builder = NbtMap.builder(); NbtMapBuilder blockBuilder = NbtMap.builder() .putString("name", "minecraft:frame") .putInt("version", BlockTranslator.getBlockStateVersion()); @@ -77,9 +76,7 @@ public class ItemFrameEntity extends Entity { .putInt("facing_direction", direction.ordinal()) .putByte("item_frame_map_bit", (byte) 0) .build()); - builder.put("block", blockBuilder.build()); - builder.putShort("id", (short) 199); - bedrockRuntimeId = BlockTranslator.getItemFrame(builder.build()); + bedrockRuntimeId = BlockTranslator.getItemFrame(blockBuilder.build()); bedrockPosition = Vector3i.from(position.getFloorX(), position.getFloorY(), position.getFloorZ()); } @@ -101,7 +98,7 @@ public class ItemFrameEntity extends Entity { ItemEntry itemEntry = ItemRegistry.getItem((ItemStack) entityMetadata.getValue()); NbtMapBuilder builder = NbtMap.builder(); - String blockName = ItemRegistry.getBedrockIdentifer(itemEntry); + String blockName = ItemRegistry.getBedrockIdentifier(itemEntry); builder.putByte("Count", (byte) itemData.getCount()); if (itemData.getTag() != null) { @@ -141,8 +138,7 @@ public class ItemFrameEntity extends Entity { UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket(); updateBlockPacket.setDataLayer(0); updateBlockPacket.setBlockPosition(bedrockPosition); - // TODO 1.16.100 set to BEDROCK_AIR - updateBlockPacket.setRuntimeId(0); + updateBlockPacket.setRuntimeId(BlockTranslator.BEDROCK_AIR_ID); updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.PRIORITY); updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NETWORK); updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NEIGHBORS); diff --git a/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java b/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java index 5d4462b4..85043d37 100644 --- a/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java +++ b/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java @@ -26,8 +26,7 @@ package org.geysermc.connector.network; import com.nukkitx.protocol.bedrock.BedrockPacketCodec; -import com.nukkitx.protocol.bedrock.v407.Bedrock_v407; -import com.nukkitx.protocol.bedrock.v408.Bedrock_v408; +import com.nukkitx.protocol.bedrock.v419.Bedrock_v419; import java.util.ArrayList; import java.util.List; @@ -40,14 +39,13 @@ public class BedrockProtocol { * 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_v408.V408_CODEC; + public static final BedrockPacketCodec DEFAULT_BEDROCK_CODEC = Bedrock_v419.V419_CODEC; /** * A list of all supported Bedrock versions that can join Geyser */ public static final List SUPPORTED_BEDROCK_CODECS = new ArrayList<>(); static { - SUPPORTED_BEDROCK_CODECS.add(Bedrock_v407.V407_CODEC); SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC); } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 00b48a56..b8722122 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -75,7 +75,6 @@ import org.geysermc.connector.network.translators.EntityIdentifierRegistry; import org.geysermc.connector.network.translators.PacketTranslatorRegistry; import org.geysermc.connector.network.translators.inventory.EnchantmentInventoryTranslator; import org.geysermc.connector.network.translators.item.ItemRegistry; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.utils.*; import org.geysermc.floodgate.util.BedrockData; import org.geysermc.floodgate.util.EncryptionUtil; @@ -674,7 +673,6 @@ public class GeyserSession implements CommandSender { // startGamePacket.setCurrentTick(0); startGamePacket.setEnchantmentSeed(0); startGamePacket.setMultiplayerCorrelationId(""); - startGamePacket.setBlockPalette(BlockTranslator.BLOCKS); startGamePacket.setItemEntries(ItemRegistry.ITEMS); startGamePacket.setVanillaVersion("*"); startGamePacket.setAuthoritativeMovementMode(AuthoritativeMovementMode.CLIENT); diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java index 7bf84b8d..cbf3721f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java @@ -94,12 +94,12 @@ public class ChunkCache { public int getBlockAt(int x, int y, int z) { if (!cache) { - return BlockTranslator.AIR; + return BlockTranslator.JAVA_AIR_ID; } Column column = this.getChunk(x >> 4, z >> 4); if (column == null) { - return BlockTranslator.AIR; + return BlockTranslator.JAVA_AIR_ID; } Chunk chunk = column.getChunks()[y >> 4]; @@ -107,7 +107,7 @@ public class ChunkCache { return chunk.get(x & 0xF, y & 0xF, z & 0xF); } - return BlockTranslator.AIR; + return BlockTranslator.JAVA_AIR_ID; } public void removeChunk(int chunkX, int chunkZ) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestTranslator.java index 023a83af..3e40ddd6 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestTranslator.java @@ -42,7 +42,7 @@ public class BedrockBlockPickRequestTranslator extends PacketTranslator> itemEntriesType = new TypeReference>() { }; @@ -104,8 +110,13 @@ public class ItemRegistry { throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.toolbox.fail.runtime_bedrock"), e); } + int lodestoneCompassId = 0; + for (JsonNode entry : itemEntries) { ITEMS.add(new StartGamePacket.ItemEntry(entry.get("name").textValue(), (short) entry.get("id").intValue())); + if (entry.get("name").textValue().equals("minecraft:lodestone_compass")) { + lodestoneCompassId = entry.get("id").intValue(); + } } stream = FileUtils.getResource("mappings/items.json"); @@ -153,9 +164,6 @@ public class ItemRegistry { case "minecraft:bamboo": BAMBOO = ITEM_ENTRIES.get(itemIndex); break; - case "minecraft:oak_boat": - BOAT = ITEM_ENTRIES.get(itemIndex); - break; case "minecraft:egg": EGG = ITEM_ENTRIES.get(itemIndex); break; @@ -165,8 +173,8 @@ public class ItemRegistry { case "minecraft:shield": SHIELD = ITEM_ENTRIES.get(itemIndex); break; - case "minecraft:bucket": - BUCKET = ITEM_ENTRIES.get(itemIndex); + case "minecraft:milk_bucket": + MILK_BUCKET = ITEM_ENTRIES.get(itemIndex); break; case "minecraft:wheat": WHEAT = ITEM_ENTRIES.get(itemIndex); @@ -175,11 +183,22 @@ public class ItemRegistry { break; } + if (entry.getKey().contains("boat")) { + BOATS.add(entry.getValue().get("bedrock_id").intValue()); + } else if (entry.getKey().contains("bucket") && !entry.getKey().contains("milk")) { + BUCKETS.add(entry.getValue().get("bedrock_id").intValue()); + } + itemIndex++; } - // Add the loadstonecompass since it doesn't exist on java but we need it for item conversion - ITEM_ENTRIES.put(itemIndex, new ItemEntry("minecraft:lodestonecompass", itemIndex, 741, 0, false)); + if (lodestoneCompassId == 0) { + throw new RuntimeException("Lodestone compass not found in item palette!"); + } + + // Add the loadstone compass since it doesn't exist on java but we need it for item conversion + ITEM_ENTRIES.put(itemIndex, new ItemEntry("minecraft:lodestone_compass", itemIndex, + lodestoneCompassId, 0, false)); /* Load creative items */ stream = FileUtils.getResource("bedrock/creative_items.json"); @@ -256,7 +275,7 @@ public class ItemRegistry { * @param entry the ItemEntry to search for * @return the Bedrock identifier */ - public static String getBedrockIdentifer(ItemEntry entry) { + public static String getBedrockIdentifier(ItemEntry entry) { String blockName = ""; for (StartGamePacket.ItemEntry startGamePacketItemEntry : ItemRegistry.ITEMS) { if (startGamePacketItemEntry.getId() == (short) entry.getBedrockId()) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/CrossbowTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/CrossbowTranslator.java index 67f137ff..a05b9c8b 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/CrossbowTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/CrossbowTranslator.java @@ -53,7 +53,7 @@ public class CrossbowTranslator extends NbtItemStackTranslator { CompoundTag newProjectile = new CompoundTag("chargedItem"); newProjectile.put(new ByteTag("Count", (byte) itemData.getCount())); - newProjectile.put(new StringTag("Name", ItemRegistry.getBedrockIdentifer(entry))); + newProjectile.put(new StringTag("Name", ItemRegistry.getBedrockIdentifier(entry))); newProjectile.put(new ShortTag("Damage", itemData.getDamage())); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java index a9930f69..6ecb9a44 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java @@ -50,7 +50,7 @@ public class ShulkerBoxItemTranslator extends NbtItemStackTranslator { boxItemTag.put(new ByteTag("WasPickedUp", (byte) 0)); // ??? ItemEntry boxItemEntry = ItemRegistry.getItemEntry(((StringTag) itemData.get("id")).getValue()); - String blockName = ItemRegistry.getBedrockIdentifer(boxItemEntry); + String blockName = ItemRegistry.getBedrockIdentifier(boxItemEntry); boxItemTag.put(new StringTag("Name", blockName)); boxItemTag.put(new ShortTag("Damage", (short) boxItemEntry.getBedrockData())); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaExplosionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaExplosionTranslator.java index efed6ba4..4a0ea3ec 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaExplosionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaExplosionTranslator.java @@ -46,7 +46,7 @@ public class JavaExplosionTranslator extends PacketTranslator BLOCKS; - public static final int AIR = 0; + /** + * The Java block runtime ID of air + */ + public static final int JAVA_AIR_ID = 0; + /** + * The Bedrock block runtime ID of air + */ + public static final int BEDROCK_AIR_ID; public static final int BEDROCK_WATER_ID; private static final Int2IntMap JAVA_TO_BEDROCK_BLOCK_MAP = new Int2IntOpenHashMap(); @@ -86,25 +90,31 @@ public class BlockTranslator { public static final int JAVA_RUNTIME_SPAWNER_ID; - private static final int BLOCK_STATE_VERSION = 17825806; + private static final int BLOCK_STATE_VERSION = 17825808; static { /* Load block palette */ - InputStream stream = FileUtils.getResource("bedrock/runtime_block_states.dat"); + InputStream stream = FileUtils.getResource("bedrock/blockpalette.nbt"); NbtList blocksTag; - try (NBTInputStream nbtInputStream = NbtUtils.createNetworkReader(stream)) { - blocksTag = (NbtList) nbtInputStream.readTag(); + try (NBTInputStream nbtInputStream = new NBTInputStream(new DataInputStream(stream))) { + NbtMap blockPalette = (NbtMap) nbtInputStream.readTag(); + blocksTag = (NbtList) blockPalette.getList("blocks", NbtType.COMPOUND); } catch (Exception e) { throw new AssertionError("Unable to get blocks from runtime block states", e); } - Map blockStateMap = new HashMap<>(); + // 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()); - for (NbtMap tag : blocksTag) { - if (blockStateMap.putIfAbsent(tag.getCompound("block"), tag) != null) { + for (int i = 0; i < blocksTag.size(); i++) { + NbtMap tag = blocksTag.get(i); + NbtMap blockTag = tag.getCompound("block"); + if (blockStateOrderedMap.containsKey(blockTag)) { throw new AssertionError("Duplicate block states in Bedrock palette"); } + blockStateOrderedMap.put(blockTag, i); } stream = FileUtils.getResource("mappings/blocks.json"); @@ -114,16 +124,13 @@ public class BlockTranslator { } catch (Exception e) { throw new AssertionError("Unable to load Java block mappings", e); } - Object2IntMap addedStatesMap = new Object2IntOpenHashMap<>(); - addedStatesMap.defaultReturnValue(-1); - List paletteList = new ArrayList<>(); - Reflections ref = GeyserConnector.getInstance().useXmlReflections() ? FileUtils.getReflections("org.geysermc.connector.network.translators.world.block.entity") : new Reflections("org.geysermc.connector.network.translators.world.block.entity"); - ref.getTypesAnnotatedWith(BlockEntity.class); + Reflections ref = GeyserConnector.getInstance().useXmlReflections() ? FileUtils.getReflections("org.geysermc.connector.network.translators.world.block.entity") + : new Reflections("org.geysermc.connector.network.translators.world.block.entity"); int waterRuntimeId = -1; int javaRuntimeId = -1; - int bedrockRuntimeId = 0; + int airRuntimeId = -1; int cobwebRuntimeId = -1; int commandBlockRuntimeId = -1; int furnaceRuntimeId = -1; @@ -136,6 +143,10 @@ public class BlockTranslator { Map.Entry entry = blocksIterator.next(); String javaId = entry.getKey(); NbtMap blockTag = buildBedrockState(entry.getValue()); + int bedrockRuntimeId = blockStateOrderedMap.getOrDefault(blockTag, -1); + if (bedrockRuntimeId == -1) { + throw new RuntimeException("Unable to find " + javaId + " Bedrock runtime ID!"); + } // TODO fix this, (no block should have a null hardness) JsonNode hardnessNode = entry.getValue().get("block_hardness"); @@ -199,22 +210,12 @@ public class BlockTranslator { BEDROCK_TO_JAVA_BLOCK_MAP.putIfAbsent(bedrockRuntimeId, javaRuntimeId); } - NbtMap runtimeTag = blockStateMap.remove(blockTag); - if (runtimeTag != null) { - addedStatesMap.put(blockTag, bedrockRuntimeId); - paletteList.add(runtimeTag); - } else { - int duplicateRuntimeId = addedStatesMap.getOrDefault(blockTag, -1); - if (duplicateRuntimeId == -1) { - GeyserConnector.getInstance().getLogger().debug("Mapping " + javaId + " was not found for bedrock edition!"); - } else { - JAVA_TO_BEDROCK_BLOCK_MAP.put(javaRuntimeId, duplicateRuntimeId); - } - continue; - } JAVA_TO_BEDROCK_BLOCK_MAP.put(javaRuntimeId, bedrockRuntimeId); - if (javaId.contains("wool")) { + if (bedrockIdentifier.equals("minecraft:air")) { + airRuntimeId = bedrockRuntimeId; + + } else if (javaId.contains("wool")) { JAVA_RUNTIME_WOOL_IDS.add(javaRuntimeId); } else if (javaId.contains("cobweb")) { @@ -233,8 +234,6 @@ public class BlockTranslator { } else if (javaId.startsWith("minecraft:spawner")) { spawnerRuntimeId = javaRuntimeId; } - - bedrockRuntimeId++; } if (cobwebRuntimeId == -1) { @@ -267,19 +266,17 @@ public class BlockTranslator { } BEDROCK_WATER_ID = waterRuntimeId; - paletteList.addAll(blockStateMap.values()); // Add any missing mappings that could crash the client + if (airRuntimeId == -1) { + throw new AssertionError("Unable to find air in palette"); + } + BEDROCK_AIR_ID = airRuntimeId; // Loop around again to find all item frame runtime IDs - int frameRuntimeId = 0; - for (NbtMap tag : paletteList) { - NbtMap blockTag = tag.getCompound("block"); - if (blockTag.getString("name").equals("minecraft:frame")) { - ITEM_FRAMES.put(tag, frameRuntimeId); + for (Object2IntMap.Entry entry : blockStateOrderedMap.object2IntEntrySet()) { + if (entry.getKey().getString("name").equals("minecraft:frame")) { + ITEM_FRAMES.put(entry.getKey(), entry.getIntValue()); } - frameRuntimeId++; } - - BLOCKS = new NbtList<>(NbtType.COMPOUND, paletteList); } private BlockTranslator() { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/BlockStorage.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/BlockStorage.java index d8cd7520..f195394d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/BlockStorage.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/BlockStorage.java @@ -30,6 +30,7 @@ import io.netty.buffer.ByteBuf; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; import lombok.Getter; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.network.translators.world.chunk.bitarray.BitArray; import org.geysermc.connector.network.translators.world.chunk.bitarray.BitArrayVersion; @@ -50,7 +51,7 @@ public class BlockStorage { public BlockStorage(BitArrayVersion version) { this.bitArray = version.createArray(SIZE); this.palette = new IntArrayList(16); - this.palette.add(0); // Air is at the start of every palette. + this.palette.add(BlockTranslator.BEDROCK_AIR_ID); // Air is at the start of every palette and controls what the default block is in second-layer non-air block spaces. } public BlockStorage(BitArray bitArray, IntList palette) { diff --git a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java index 70ac76df..005a4960 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java @@ -68,7 +68,8 @@ import java.util.ArrayList; import java.util.BitSet; import java.util.List; -import static org.geysermc.connector.network.translators.world.block.BlockTranslator.AIR; +import static org.geysermc.connector.network.translators.world.block.BlockTranslator.JAVA_AIR_ID; +import static org.geysermc.connector.network.translators.world.block.BlockTranslator.BEDROCK_AIR_ID; import static org.geysermc.connector.network.translators.world.block.BlockTranslator.BEDROCK_WATER_ID; @UtilityClass @@ -246,7 +247,7 @@ public class ChunkUtils { // V1 palette IntList layer1Palette = new IntArrayList(2); - layer1Palette.add(0); // Air + layer1Palette.add(BEDROCK_AIR_ID); // Air - see BlockStorage's constructor for more information layer1Palette.add(BEDROCK_WATER_ID); layers = new BlockStorage[]{ layer0, new BlockStorage(BitArrayVersion.V1.createArray(BlockStorage.SIZE, layer1Data), layer1Palette) }; @@ -318,18 +319,32 @@ public class ChunkUtils { } } + /** + * Sends a block update to the Bedrock client. If chunk caching is enabled and the platform is not Spigot, this also + * adds that block to the cache. + * @param session the Bedrock session to send/register the block to + * @param blockState the Java block state of the block + * @param position the position of the block + */ public static void updateBlock(GeyserSession session, int blockState, Position position) { Vector3i pos = Vector3i.from(position.getX(), position.getY(), position.getZ()); updateBlock(session, blockState, pos); } + /** + * Sends a block update to the Bedrock client. If chunk caching is enabled and the platform is not Spigot, this also + * adds that block to the cache. + * @param session the Bedrock session to send/register the block to + * @param blockState the Java block state of the block + * @param position the position of the block + */ public static void updateBlock(GeyserSession session, int blockState, Vector3i position) { // Checks for item frames so they aren't tripped up and removed long frameEntityId = ItemFrameEntity.getItemFrameEntityId(session, position); if (frameEntityId != -1) { // TODO: Very occasionally the item frame doesn't sync up when destroyed Entity entity = session.getEntityCache().getEntityByJavaId(frameEntityId); - if (blockState == AIR && entity != null) { // Item frame is still present and no block overrides that; refresh it + if (blockState == JAVA_AIR_ID && entity != null) { // Item frame is still present and no block overrides that; refresh it ((ItemFrameEntity) entity).updateBlock(session); return; } @@ -358,7 +373,7 @@ public class ChunkUtils { if (BlockTranslator.isWaterlogged(blockState)) { waterPacket.setRuntimeId(BEDROCK_WATER_ID); } else { - waterPacket.setRuntimeId(0); + waterPacket.setRuntimeId(BEDROCK_AIR_ID); } session.sendUpstreamPacket(waterPacket); diff --git a/connector/src/main/resources/bedrock/blockpalette.nbt b/connector/src/main/resources/bedrock/blockpalette.nbt new file mode 100644 index 0000000000000000000000000000000000000000..b92a06260912dfd8e741a6c8f559af277d6c9775 GIT binary patch literal 1160789 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 literal 0 HcmV?d00001 diff --git a/connector/src/main/resources/bedrock/creative_items.json b/connector/src/main/resources/bedrock/creative_items.json index eea92a95..90839b0d 100644 --- a/connector/src/main/resources/bedrock/creative_items.json +++ b/connector/src/main/resources/bedrock/creative_items.json @@ -258,31 +258,31 @@ "id" : -275 }, { - "id" : 324 + "id" : 359 }, { - "id" : 427 + "id" : 543 }, { - "id" : 428 + "id" : 544 }, { - "id" : 429 + "id" : 545 }, { - "id" : 430 + "id" : 546 }, { - "id" : 431 + "id" : 547 }, { - "id" : 330 + "id" : 370 }, { - "id" : 755 + "id" : 604 }, { - "id" : 756 + "id" : 605 }, { "id" : 96 @@ -314,9 +314,6 @@ { "id" : 101 }, - { - "id" : 758 - }, { "id" : 20 }, @@ -741,66 +738,15 @@ { "id" : 170 }, - { - "id" : -239 - }, { "id" : 216 }, - { - "id" : 214 - }, - { - "id" : -227 - }, { "id" : 112 }, { "id" : 215 }, - { - "id" : -225 - }, - { - "id" : -226 - }, - { - "id" : -240 - }, - { - "id" : -241 - }, - { - "id" : -299 - }, - { - "id" : -298 - }, - { - "id" : -300 - }, - { - "id" : -301 - }, - { - "id" : -230 - }, - { - "id" : -232 - }, - { - "id" : -233 - }, - { - "id" : -234 - }, - { - "id" : -235 - }, - { - "id" : -236 - }, { "id" : -270 }, @@ -1183,6 +1129,30 @@ "id" : 201, "damage" : 2 }, + { + "id" : 214 + }, + { + "id" : -227 + }, + { + "id" : -230 + }, + { + "id" : -232 + }, + { + "id" : -233 + }, + { + "id" : -234 + }, + { + "id" : -235 + }, + { + "id" : -236 + }, { "id" : 3 }, @@ -1190,6 +1160,9 @@ "id" : 3, "damage" : 1 }, + { + "id" : 60 + }, { "id" : 2 }, @@ -1318,6 +1291,18 @@ { "id" : -9 }, + { + "id" : -225 + }, + { + "id" : -240 + }, + { + "id" : -226 + }, + { + "id" : -241 + }, { "id" : -212 }, @@ -1365,6 +1350,18 @@ "id" : -212, "damage" : 13 }, + { + "id" : -299 + }, + { + "id" : -300 + }, + { + "id" : -298 + }, + { + "id" : -301 + }, { "id" : 18 }, @@ -1411,59 +1408,58 @@ "damage" : 5 }, { - "id" : -218, - "damage" : 3 + "id" : -218 + }, + { + "id" : 291 + }, + { + "id" : 292 + }, + { + "id" : 293 }, { "id" : 295 }, { - "id" : 361 + "id" : 334 }, { - "id" : 362 + "id" : 285 }, { - "id" : 458 + "id" : 280 }, { - "id" : 296 + "id" : 282 }, { - "id" : 457 + "id" : 279 }, { - "id" : 392 + "id" : 283 }, { - "id" : 394 + "id" : 257 }, { - "id" : 391 + "id" : 258 }, { - "id" : 396 - }, - { - "id" : 260 - }, - { - "id" : 322 - }, - { - "id" : 466 + "id" : 259 }, { "id" : 103 }, { - "id" : 360 + "id" : 272 }, { - "id" : 382 + "id" : 432 }, { - "id" : 477 + "id" : 287 }, { "id" : 86 @@ -1475,7 +1471,7 @@ "id" : 91 }, { - "id" : 736 + "id" : 580 }, { "id" : 31, @@ -1494,7 +1490,7 @@ "damage" : 2 }, { - "id" : 760 + "id" : 609 }, { "id" : -131, @@ -1574,7 +1570,7 @@ "damage" : 4 }, { - "id" : 335 + "id" : 380 }, { "id" : -130 @@ -1650,83 +1646,64 @@ "id" : -216 }, { - "id" : 351, - "damage" : 19 + "id" : 408 }, { - "id" : 351, - "damage" : 7 + "id" : 400 }, { - "id" : 351, - "damage" : 8 + "id" : 401 }, { - "id" : 351, - "damage" : 16 + "id" : 393 }, { - "id" : 351, - "damage" : 17 + "id" : 396 }, { - "id" : 351, - "damage" : 1 + "id" : 394 }, { - "id" : 351, - "damage" : 14 + "id" : 407 }, { - "id" : 351, - "damage" : 11 + "id" : 404 }, { - "id" : 351, - "damage" : 10 + "id" : 403 }, { - "id" : 351, - "damage" : 2 + "id" : 395 }, { - "id" : 351, - "damage" : 6 + "id" : 399 }, { - "id" : 351, - "damage" : 12 + "id" : 405 }, { - "id" : 351, - "damage" : 18 + "id" : 397 }, { - "id" : 351, - "damage" : 5 + "id" : 398 }, { - "id" : 351, - "damage" : 13 + "id" : 406 }, { - "id" : 351, - "damage" : 9 + "id" : 402 }, { - "id" : 351 + "id" : 411 }, { - "id" : 351, - "damage" : 3 + "id" : 410 }, { - "id" : 351, - "damage" : 4 + "id" : 412 }, { - "id" : 351, - "damage" : 15 + "id" : 409 }, { "id" : 106 @@ -1762,31 +1739,31 @@ "id" : 78 }, { - "id" : 365 + "id" : 275 }, { - "id" : 319 + "id" : 262 }, { - "id" : 363 + "id" : 273 }, { - "id" : 423 + "id" : 540 }, { - "id" : 411 + "id" : 288 }, { - "id" : 349 + "id" : 264 }, { - "id" : 460 + "id" : 265 }, { - "id" : 461 + "id" : 266 }, { - "id" : 462 + "id" : 267 }, { "id" : 39 @@ -1816,25 +1793,25 @@ "id" : 99 }, { - "id" : 344 + "id" : 388 }, { - "id" : 338 + "id" : 383 }, { - "id" : 353 + "id" : 414 }, { - "id" : 367 + "id" : 277 }, { - "id" : 352 + "id" : 413 }, { "id" : 30 }, { - "id" : 375 + "id" : 278 }, { "id" : 52 @@ -1869,252 +1846,193 @@ "id" : -159 }, { - "id" : 383, - "damage" : 10 + "id" : 433 }, { - "id" : 383, - "damage" : 122 + "id" : 492 }, { - "id" : 383, - "damage" : 11 + "id" : 434 }, { - "id" : 383, - "damage" : 12 + "id" : 435 }, { - "id" : 383, - "damage" : 13 + "id" : 436 }, { - "id" : 383, - "damage" : 14 + "id" : 437 }, { - "id" : 383, - "damage" : 28 + "id" : 470 }, { - "id" : 383, - "damage" : 22 + "id" : 449 }, { - "id" : 383, - "damage" : 75 + "id" : 486 }, { - "id" : 383, - "damage" : 16 + "id" : 438 }, { - "id" : 383, - "damage" : 19 + "id" : 451 }, { - "id" : 383, - "damage" : 30 + "id" : 476 }, { - "id" : 383, - "damage" : 18 + "id" : 457 }, { - "id" : 383, - "damage" : 29 + "id" : 471 }, { - "id" : 383, - "damage" : 23 + "id" : 456 }, { - "id" : 383, - "damage" : 24 + "id" : 463 }, { - "id" : 383, - "damage" : 25 + "id" : 464 }, { - "id" : 383, - "damage" : 26 + "id" : 465 }, { - "id" : 383, - "damage" : 27 + "id" : 466 }, { - "id" : 383, - "damage" : 111 + "id" : 477 }, { - "id" : 383, - "damage" : 112 + "id" : 478 }, { - "id" : 383, - "damage" : 108 + "id" : 479 }, { - "id" : 383, - "damage" : 109 + "id" : 480 }, { - "id" : 383, - "damage" : 31 + "id" : 482 }, { - "id" : 383, - "damage" : 74 + "id" : 483 }, { - "id" : 383, - "damage" : 113 + "id" : 487 }, { - "id" : 383, - "damage" : 121 + "id" : 488 }, { - "id" : 383, - "damage" : 33 + "id" : 439 }, { - "id" : 383, - "damage" : 38 + "id" : 440 }, { - "id" : 383, - "damage" : 39 + "id" : 441 }, { - "id" : 383, - "damage" : 34 + "id" : 442 }, { - "id" : 383, - "damage" : 48 + "id" : 462 }, { - "id" : 383, - "damage" : 46 + "id" : 460 }, { - "id" : 383, - "damage" : 37 + "id" : 443 }, { - "id" : 383, - "damage" : 35 + "id" : 444 }, { - "id" : 383, - "damage" : 32 + "id" : 445 }, { - "id" : 383, - "damage" : 36 + "id" : 446 }, { - "id" : 383, - "damage" : 47 + "id" : 461 }, { - "id" : 383, - "damage" : 110 + "id" : 481 }, { - "id" : 383, - "damage" : 17 + "id" : 448 }, { - "id" : 383, - "damage" : 40 + "id" : 455 }, { - "id" : 383, - "damage" : 45 + "id" : 450 }, { - "id" : 383, - "damage" : 49 + "id" : 459 }, { - "id" : 383, - "damage" : 50 + "id" : 469 }, { - "id" : 383, - "damage" : 55 + "id" : 458 }, { - "id" : 383, - "damage" : 42 + "id" : 453 }, { - "id" : 383, - "damage" : 125 + "id" : 493 }, { - "id" : 383, - "damage" : 124 + "id" : 494 }, { - "id" : 383, - "damage" : 123 + "id" : 495 }, { - "id" : 383, - "damage" : 126 + "id" : 496 }, { - "id" : 383, - "damage" : 41 + "id" : 497 }, { - "id" : 383, - "damage" : 43 + "id" : 452 }, { - "id" : 383, - "damage" : 54 + "id" : 454 }, { - "id" : 383, - "damage" : 57 + "id" : 467 }, { - "id" : 383, - "damage" : 104 + "id" : 472 }, { - "id" : 383, - "damage" : 105 + "id" : 473 }, { - "id" : 383, - "damage" : 115 + "id" : 474 }, { - "id" : 383, - "damage" : 118 + "id" : 447 }, { - "id" : 383, - "damage" : 116 + "id" : 490 }, { - "id" : 383, - "damage" : 58 + "id" : 475 }, { - "id" : 383, - "damage" : 114 + "id" : 484 }, { - "id" : 383, - "damage" : 59 + "id" : 489 + }, + { + "id" : 491 }, { "id" : 49 @@ -2135,7 +2053,7 @@ "id" : 213 }, { - "id" : 372 + "id" : 294 }, { "id" : 121 @@ -2147,10 +2065,10 @@ "id" : 240 }, { - "id" : 432 + "id" : 548 }, { - "id" : 433 + "id" : 549 }, { "id" : 19 @@ -2199,1023 +2117,1023 @@ "damage" : 12 }, { - "id" : 298 + "id" : 335 }, { - "id" : 302 + "id" : 339 }, { - "id" : 306 + "id" : 343 }, { - "id" : 314 - }, - { - "id" : 310 - }, - { - "id" : 748 - }, - { - "id" : 299 - }, - { - "id" : 303 - }, - { - "id" : 307 - }, - { - "id" : 315 - }, - { - "id" : 311 - }, - { - "id" : 749 - }, - { - "id" : 300 - }, - { - "id" : 304 - }, - { - "id" : 308 - }, - { - "id" : 316 - }, - { - "id" : 312 - }, - { - "id" : 750 - }, - { - "id" : 301 - }, - { - "id" : 305 - }, - { - "id" : 309 - }, - { - "id" : 317 - }, - { - "id" : 313 - }, - { - "id" : 751 - }, - { - "id" : 268 - }, - { - "id" : 272 - }, - { - "id" : 267 - }, - { - "id" : 283 - }, - { - "id" : 276 - }, - { - "id" : 743 - }, - { - "id" : 271 - }, - { - "id" : 275 - }, - { - "id" : 258 - }, - { - "id" : 286 - }, - { - "id" : 279 - }, - { - "id" : 746 - }, - { - "id" : 270 - }, - { - "id" : 274 - }, - { - "id" : 257 - }, - { - "id" : 285 - }, - { - "id" : 278 - }, - { - "id" : 745 - }, - { - "id" : 269 - }, - { - "id" : 273 - }, - { - "id" : 256 - }, - { - "id" : 284 - }, - { - "id" : 277 - }, - { - "id" : 744 - }, - { - "id" : 290 - }, - { - "id" : 291 - }, - { - "id" : 292 - }, - { - "id" : 294 - }, - { - "id" : 293 - }, - { - "id" : 747 - }, - { - "id" : 261 - }, - { - "id" : 471 - }, - { - "id" : 262 - }, - { - "id" : 262, - "damage" : 6 - }, - { - "id" : 262, - "damage" : 7 - }, - { - "id" : 262, - "damage" : 8 - }, - { - "id" : 262, - "damage" : 9 - }, - { - "id" : 262, - "damage" : 10 - }, - { - "id" : 262, - "damage" : 11 - }, - { - "id" : 262, - "damage" : 12 - }, - { - "id" : 262, - "damage" : 13 - }, - { - "id" : 262, - "damage" : 14 - }, - { - "id" : 262, - "damage" : 15 - }, - { - "id" : 262, - "damage" : 16 - }, - { - "id" : 262, - "damage" : 17 - }, - { - "id" : 262, - "damage" : 18 - }, - { - "id" : 262, - "damage" : 19 - }, - { - "id" : 262, - "damage" : 20 - }, - { - "id" : 262, - "damage" : 21 - }, - { - "id" : 262, - "damage" : 22 - }, - { - "id" : 262, - "damage" : 23 - }, - { - "id" : 262, - "damage" : 24 - }, - { - "id" : 262, - "damage" : 25 - }, - { - "id" : 262, - "damage" : 26 - }, - { - "id" : 262, - "damage" : 27 - }, - { - "id" : 262, - "damage" : 28 - }, - { - "id" : 262, - "damage" : 29 - }, - { - "id" : 262, - "damage" : 30 - }, - { - "id" : 262, - "damage" : 31 - }, - { - "id" : 262, - "damage" : 32 - }, - { - "id" : 262, - "damage" : 33 - }, - { - "id" : 262, - "damage" : 34 - }, - { - "id" : 262, - "damage" : 35 - }, - { - "id" : 262, - "damage" : 36 - }, - { - "id" : 262, - "damage" : 37 - }, - { - "id" : 262, - "damage" : 38 - }, - { - "id" : 262, - "damage" : 39 - }, - { - "id" : 262, - "damage" : 40 - }, - { - "id" : 262, - "damage" : 41 - }, - { - "id" : 262, - "damage" : 42 - }, - { - "id" : 262, - "damage" : 43 - }, - { - "id" : 513 - }, - { - "id" : 366 - }, - { - "id" : 320 - }, - { - "id" : 364 - }, - { - "id" : 424 - }, - { - "id" : 412 - }, - { - "id" : 350 - }, - { - "id" : 463 - }, - { - "id" : 297 - }, - { - "id" : 282 - }, - { - "id" : 459 - }, - { - "id" : 413 - }, - { - "id" : 393 - }, - { - "id" : 357 - }, - { - "id" : 400 - }, - { - "id" : 354 - }, - { - "id" : 464 - }, - { - "id" : 346 - }, - { - "id" : 398 - }, - { - "id" : 757 - }, - { - "id" : 332 - }, - { - "id" : 359 - }, - { - "id" : 259 - }, - { - "id" : 420 + "id" : 351 }, { "id" : 347 }, + { + "id" : 597 + }, + { + "id" : 336 + }, + { + "id" : 340 + }, + { + "id" : 344 + }, + { + "id" : 352 + }, + { + "id" : 348 + }, + { + "id" : 598 + }, + { + "id" : 337 + }, + { + "id" : 341 + }, { "id" : 345 }, { - "id" : 395 + "id" : 353 }, { - "id" : 395, - "damage" : 2 + "id" : 349 + }, + { + "id" : 599 + }, + { + "id" : 338 + }, + { + "id" : 342 + }, + { + "id" : 346 + }, + { + "id" : 354 + }, + { + "id" : 350 + }, + { + "id" : 600 + }, + { + "id" : 308 + }, + { + "id" : 312 + }, + { + "id" : 307 + }, + { + "id" : 322 + }, + { + "id" : 316 + }, + { + "id" : 592 + }, + { + "id" : 311 + }, + { + "id" : 315 + }, + { + "id" : 298 + }, + { + "id" : 325 + }, + { + "id" : 319 + }, + { + "id" : 595 + }, + { + "id" : 310 + }, + { + "id" : 314 + }, + { + "id" : 297 + }, + { + "id" : 324 + }, + { + "id" : 318 + }, + { + "id" : 594 + }, + { + "id" : 309 + }, + { + "id" : 313 + }, + { + "id" : 296 + }, + { + "id" : 323 + }, + { + "id" : 317 + }, + { + "id" : 593 }, { "id" : 329 }, { - "id" : 416 + "id" : 330 }, { - "id" : 417 + "id" : 331 }, { - "id" : 418 + "id" : 333 }, { - "id" : 419 + "id" : 332 }, { - "id" : 455 + "id" : 596 }, { - "id" : 469 + "id" : 300 }, { - "id" : 444 + "id" : 565 }, { - "id" : 450 + "id" : 301 }, { - "id" : 374 - }, - { - "id" : 384 - }, - { - "id" : 373 - }, - { - "id" : 373, - "damage" : 1 - }, - { - "id" : 373, - "damage" : 2 - }, - { - "id" : 373, - "damage" : 3 - }, - { - "id" : 373, - "damage" : 4 - }, - { - "id" : 373, - "damage" : 5 - }, - { - "id" : 373, + "id" : 301, "damage" : 6 }, { - "id" : 373, + "id" : 301, "damage" : 7 }, { - "id" : 373, + "id" : 301, "damage" : 8 }, { - "id" : 373, + "id" : 301, "damage" : 9 }, { - "id" : 373, + "id" : 301, "damage" : 10 }, { - "id" : 373, + "id" : 301, "damage" : 11 }, { - "id" : 373, + "id" : 301, "damage" : 12 }, { - "id" : 373, + "id" : 301, "damage" : 13 }, { - "id" : 373, + "id" : 301, "damage" : 14 }, { - "id" : 373, + "id" : 301, "damage" : 15 }, { - "id" : 373, + "id" : 301, "damage" : 16 }, { - "id" : 373, + "id" : 301, "damage" : 17 }, { - "id" : 373, + "id" : 301, "damage" : 18 }, { - "id" : 373, + "id" : 301, "damage" : 19 }, { - "id" : 373, + "id" : 301, "damage" : 20 }, { - "id" : 373, + "id" : 301, "damage" : 21 }, { - "id" : 373, + "id" : 301, "damage" : 22 }, { - "id" : 373, + "id" : 301, "damage" : 23 }, { - "id" : 373, + "id" : 301, "damage" : 24 }, { - "id" : 373, + "id" : 301, "damage" : 25 }, { - "id" : 373, + "id" : 301, "damage" : 26 }, { - "id" : 373, + "id" : 301, "damage" : 27 }, { - "id" : 373, + "id" : 301, "damage" : 28 }, { - "id" : 373, + "id" : 301, "damage" : 29 }, { - "id" : 373, + "id" : 301, "damage" : 30 }, { - "id" : 373, + "id" : 301, "damage" : 31 }, { - "id" : 373, + "id" : 301, "damage" : 32 }, { - "id" : 373, + "id" : 301, "damage" : 33 }, { - "id" : 373, + "id" : 301, "damage" : 34 }, { - "id" : 373, + "id" : 301, "damage" : 35 }, { - "id" : 373, + "id" : 301, "damage" : 36 }, { - "id" : 373, + "id" : 301, "damage" : 37 }, { - "id" : 373, + "id" : 301, "damage" : 38 }, { - "id" : 373, + "id" : 301, "damage" : 39 }, { - "id" : 373, + "id" : 301, "damage" : 40 }, { - "id" : 373, + "id" : 301, "damage" : 41 }, { - "id" : 373, + "id" : 301, "damage" : 42 }, { - "id" : 438 - }, - { - "id" : 438, - "damage" : 1 - }, - { - "id" : 438, - "damage" : 2 - }, - { - "id" : 438, - "damage" : 3 - }, - { - "id" : 438, - "damage" : 4 - }, - { - "id" : 438, - "damage" : 5 - }, - { - "id" : 438, - "damage" : 6 - }, - { - "id" : 438, - "damage" : 7 - }, - { - "id" : 438, - "damage" : 8 - }, - { - "id" : 438, - "damage" : 9 - }, - { - "id" : 438, - "damage" : 10 - }, - { - "id" : 438, - "damage" : 11 - }, - { - "id" : 438, - "damage" : 12 - }, - { - "id" : 438, - "damage" : 13 - }, - { - "id" : 438, - "damage" : 14 - }, - { - "id" : 438, - "damage" : 15 - }, - { - "id" : 438, - "damage" : 16 - }, - { - "id" : 438, - "damage" : 17 - }, - { - "id" : 438, - "damage" : 18 - }, - { - "id" : 438, - "damage" : 19 - }, - { - "id" : 438, - "damage" : 20 - }, - { - "id" : 438, - "damage" : 21 - }, - { - "id" : 438, - "damage" : 22 - }, - { - "id" : 438, - "damage" : 23 - }, - { - "id" : 438, - "damage" : 24 - }, - { - "id" : 438, - "damage" : 25 - }, - { - "id" : 438, - "damage" : 26 - }, - { - "id" : 438, - "damage" : 27 - }, - { - "id" : 438, - "damage" : 28 - }, - { - "id" : 438, - "damage" : 29 - }, - { - "id" : 438, - "damage" : 30 - }, - { - "id" : 438, - "damage" : 31 - }, - { - "id" : 438, - "damage" : 32 - }, - { - "id" : 438, - "damage" : 33 - }, - { - "id" : 438, - "damage" : 34 - }, - { - "id" : 438, - "damage" : 35 - }, - { - "id" : 438, - "damage" : 36 - }, - { - "id" : 438, - "damage" : 37 - }, - { - "id" : 438, - "damage" : 38 - }, - { - "id" : 438, - "damage" : 39 - }, - { - "id" : 438, - "damage" : 40 - }, - { - "id" : 438, - "damage" : 41 - }, - { - "id" : 438, - "damage" : 42 - }, - { - "id" : 441 - }, - { - "id" : 441, - "damage" : 1 - }, - { - "id" : 441, - "damage" : 2 - }, - { - "id" : 441, - "damage" : 3 - }, - { - "id" : 441, - "damage" : 4 - }, - { - "id" : 441, - "damage" : 5 - }, - { - "id" : 441, - "damage" : 6 - }, - { - "id" : 441, - "damage" : 7 - }, - { - "id" : 441, - "damage" : 8 - }, - { - "id" : 441, - "damage" : 9 - }, - { - "id" : 441, - "damage" : 10 - }, - { - "id" : 441, - "damage" : 11 - }, - { - "id" : 441, - "damage" : 12 - }, - { - "id" : 441, - "damage" : 13 - }, - { - "id" : 441, - "damage" : 14 - }, - { - "id" : 441, - "damage" : 15 - }, - { - "id" : 441, - "damage" : 16 - }, - { - "id" : 441, - "damage" : 17 - }, - { - "id" : 441, - "damage" : 18 - }, - { - "id" : 441, - "damage" : 19 - }, - { - "id" : 441, - "damage" : 20 - }, - { - "id" : 441, - "damage" : 21 - }, - { - "id" : 441, - "damage" : 22 - }, - { - "id" : 441, - "damage" : 23 - }, - { - "id" : 441, - "damage" : 24 - }, - { - "id" : 441, - "damage" : 25 - }, - { - "id" : 441, - "damage" : 26 - }, - { - "id" : 441, - "damage" : 27 - }, - { - "id" : 441, - "damage" : 28 - }, - { - "id" : 441, - "damage" : 29 - }, - { - "id" : 441, - "damage" : 30 - }, - { - "id" : 441, - "damage" : 31 - }, - { - "id" : 441, - "damage" : 32 - }, - { - "id" : 441, - "damage" : 33 - }, - { - "id" : 441, - "damage" : 34 - }, - { - "id" : 441, - "damage" : 35 - }, - { - "id" : 441, - "damage" : 36 - }, - { - "id" : 441, - "damage" : 37 - }, - { - "id" : 441, - "damage" : 38 - }, - { - "id" : 441, - "damage" : 39 - }, - { - "id" : 441, - "damage" : 40 - }, - { - "id" : 441, - "damage" : 41 - }, - { - "id" : 441, - "damage" : 42 - }, - { - "id" : 280 + "id" : 301, + "damage" : 43 }, { "id" : 355 }, { - "id" : 355, - "damage" : 8 + "id" : 276 }, { - "id" : 355, - "damage" : 7 + "id" : 263 }, { - "id" : 355, - "damage" : 15 + "id" : 274 }, { - "id" : 355, - "damage" : 12 + "id" : 541 }, { - "id" : 355, - "damage" : 14 + "id" : 289 }, { - "id" : 355, - "damage" : 1 + "id" : 268 }, { - "id" : 355, - "damage" : 4 + "id" : 269 }, { - "id" : 355, - "damage" : 5 + "id" : 261 }, { - "id" : 355, - "damage" : 13 + "id" : 260 }, { - "id" : 355, - "damage" : 9 + "id" : 286 }, { - "id" : 355, - "damage" : 3 + "id" : 290 }, { - "id" : 355, - "damage" : 11 + "id" : 281 }, { - "id" : 355, - "damage" : 10 + "id" : 271 }, { - "id" : 355, + "id" : 284 + }, + { + "id" : 415 + }, + { + "id" : 270 + }, + { + "id" : 390 + }, + { + "id" : 507 + }, + { + "id" : 606 + }, + { + "id" : 372 + }, + { + "id" : 419 + }, + { + "id" : 299 + }, + { + "id" : 537 + }, + { + "id" : 391 + }, + { + "id" : 389 + }, + { + "id" : 505 + }, + { + "id" : 505, "damage" : 2 }, { - "id" : 355, + "id" : 369 + }, + { + "id" : 520 + }, + { + "id" : 521 + }, + { + "id" : 522 + }, + { + "id" : 523 + }, + { + "id" : 536 + }, + { + "id" : 563 + }, + { + "id" : 554 + }, + { + "id" : 558 + }, + { + "id" : 425 + }, + { + "id" : 498 + }, + { + "id" : 424 + }, + { + "id" : 424, + "damage" : 1 + }, + { + "id" : 424, + "damage" : 2 + }, + { + "id" : 424, + "damage" : 3 + }, + { + "id" : 424, + "damage" : 4 + }, + { + "id" : 424, + "damage" : 5 + }, + { + "id" : 424, + "damage" : 6 + }, + { + "id" : 424, + "damage" : 7 + }, + { + "id" : 424, + "damage" : 8 + }, + { + "id" : 424, + "damage" : 9 + }, + { + "id" : 424, + "damage" : 10 + }, + { + "id" : 424, + "damage" : 11 + }, + { + "id" : 424, + "damage" : 12 + }, + { + "id" : 424, + "damage" : 13 + }, + { + "id" : 424, + "damage" : 14 + }, + { + "id" : 424, + "damage" : 15 + }, + { + "id" : 424, + "damage" : 16 + }, + { + "id" : 424, + "damage" : 17 + }, + { + "id" : 424, + "damage" : 18 + }, + { + "id" : 424, + "damage" : 19 + }, + { + "id" : 424, + "damage" : 20 + }, + { + "id" : 424, + "damage" : 21 + }, + { + "id" : 424, + "damage" : 22 + }, + { + "id" : 424, + "damage" : 23 + }, + { + "id" : 424, + "damage" : 24 + }, + { + "id" : 424, + "damage" : 25 + }, + { + "id" : 424, + "damage" : 26 + }, + { + "id" : 424, + "damage" : 27 + }, + { + "id" : 424, + "damage" : 28 + }, + { + "id" : 424, + "damage" : 29 + }, + { + "id" : 424, + "damage" : 30 + }, + { + "id" : 424, + "damage" : 31 + }, + { + "id" : 424, + "damage" : 32 + }, + { + "id" : 424, + "damage" : 33 + }, + { + "id" : 424, + "damage" : 34 + }, + { + "id" : 424, + "damage" : 35 + }, + { + "id" : 424, + "damage" : 36 + }, + { + "id" : 424, + "damage" : 37 + }, + { + "id" : 424, + "damage" : 38 + }, + { + "id" : 424, + "damage" : 39 + }, + { + "id" : 424, + "damage" : 40 + }, + { + "id" : 424, + "damage" : 41 + }, + { + "id" : 424, + "damage" : 42 + }, + { + "id" : 551 + }, + { + "id" : 551, + "damage" : 1 + }, + { + "id" : 551, + "damage" : 2 + }, + { + "id" : 551, + "damage" : 3 + }, + { + "id" : 551, + "damage" : 4 + }, + { + "id" : 551, + "damage" : 5 + }, + { + "id" : 551, + "damage" : 6 + }, + { + "id" : 551, + "damage" : 7 + }, + { + "id" : 551, + "damage" : 8 + }, + { + "id" : 551, + "damage" : 9 + }, + { + "id" : 551, + "damage" : 10 + }, + { + "id" : 551, + "damage" : 11 + }, + { + "id" : 551, + "damage" : 12 + }, + { + "id" : 551, + "damage" : 13 + }, + { + "id" : 551, + "damage" : 14 + }, + { + "id" : 551, + "damage" : 15 + }, + { + "id" : 551, + "damage" : 16 + }, + { + "id" : 551, + "damage" : 17 + }, + { + "id" : 551, + "damage" : 18 + }, + { + "id" : 551, + "damage" : 19 + }, + { + "id" : 551, + "damage" : 20 + }, + { + "id" : 551, + "damage" : 21 + }, + { + "id" : 551, + "damage" : 22 + }, + { + "id" : 551, + "damage" : 23 + }, + { + "id" : 551, + "damage" : 24 + }, + { + "id" : 551, + "damage" : 25 + }, + { + "id" : 551, + "damage" : 26 + }, + { + "id" : 551, + "damage" : 27 + }, + { + "id" : 551, + "damage" : 28 + }, + { + "id" : 551, + "damage" : 29 + }, + { + "id" : 551, + "damage" : 30 + }, + { + "id" : 551, + "damage" : 31 + }, + { + "id" : 551, + "damage" : 32 + }, + { + "id" : 551, + "damage" : 33 + }, + { + "id" : 551, + "damage" : 34 + }, + { + "id" : 551, + "damage" : 35 + }, + { + "id" : 551, + "damage" : 36 + }, + { + "id" : 551, + "damage" : 37 + }, + { + "id" : 551, + "damage" : 38 + }, + { + "id" : 551, + "damage" : 39 + }, + { + "id" : 551, + "damage" : 40 + }, + { + "id" : 551, + "damage" : 41 + }, + { + "id" : 551, + "damage" : 42 + }, + { + "id" : 552 + }, + { + "id" : 552, + "damage" : 1 + }, + { + "id" : 552, + "damage" : 2 + }, + { + "id" : 552, + "damage" : 3 + }, + { + "id" : 552, + "damage" : 4 + }, + { + "id" : 552, + "damage" : 5 + }, + { + "id" : 552, + "damage" : 6 + }, + { + "id" : 552, + "damage" : 7 + }, + { + "id" : 552, + "damage" : 8 + }, + { + "id" : 552, + "damage" : 9 + }, + { + "id" : 552, + "damage" : 10 + }, + { + "id" : 552, + "damage" : 11 + }, + { + "id" : 552, + "damage" : 12 + }, + { + "id" : 552, + "damage" : 13 + }, + { + "id" : 552, + "damage" : 14 + }, + { + "id" : 552, + "damage" : 15 + }, + { + "id" : 552, + "damage" : 16 + }, + { + "id" : 552, + "damage" : 17 + }, + { + "id" : 552, + "damage" : 18 + }, + { + "id" : 552, + "damage" : 19 + }, + { + "id" : 552, + "damage" : 20 + }, + { + "id" : 552, + "damage" : 21 + }, + { + "id" : 552, + "damage" : 22 + }, + { + "id" : 552, + "damage" : 23 + }, + { + "id" : 552, + "damage" : 24 + }, + { + "id" : 552, + "damage" : 25 + }, + { + "id" : 552, + "damage" : 26 + }, + { + "id" : 552, + "damage" : 27 + }, + { + "id" : 552, + "damage" : 28 + }, + { + "id" : 552, + "damage" : 29 + }, + { + "id" : 552, + "damage" : 30 + }, + { + "id" : 552, + "damage" : 31 + }, + { + "id" : 552, + "damage" : 32 + }, + { + "id" : 552, + "damage" : 33 + }, + { + "id" : 552, + "damage" : 34 + }, + { + "id" : 552, + "damage" : 35 + }, + { + "id" : 552, + "damage" : 36 + }, + { + "id" : 552, + "damage" : 37 + }, + { + "id" : 552, + "damage" : 38 + }, + { + "id" : 552, + "damage" : 39 + }, + { + "id" : 552, + "damage" : 40 + }, + { + "id" : 552, + "damage" : 41 + }, + { + "id" : 552, + "damage" : 42 + }, + { + "id" : 320 + }, + { + "id" : 416 + }, + { + "id" : 416, + "damage" : 8 + }, + { + "id" : 416, + "damage" : 7 + }, + { + "id" : 416, + "damage" : 15 + }, + { + "id" : 416, + "damage" : 12 + }, + { + "id" : 416, + "damage" : 14 + }, + { + "id" : 416, + "damage" : 1 + }, + { + "id" : 416, + "damage" : 4 + }, + { + "id" : 416, + "damage" : 5 + }, + { + "id" : 416, + "damage" : 13 + }, + { + "id" : 416, + "damage" : 9 + }, + { + "id" : 416, + "damage" : 3 + }, + { + "id" : 416, + "damage" : 11 + }, + { + "id" : 416, + "damage" : 10 + }, + { + "id" : 416, + "damage" : 2 + }, + { + "id" : 416, "damage" : 6 }, { @@ -3246,14 +3164,13 @@ "id" : -202 }, { - "id" : -219, - "damage" : 3 + "id" : -219 }, { - "id" : 720 + "id" : 578 }, { - "id" : 801 + "id" : 610 }, { "id" : 61 @@ -3268,7 +3185,7 @@ "id" : -272 }, { - "id" : 379 + "id" : 429 }, { "id" : 145 @@ -3294,7 +3211,7 @@ "id" : -194 }, { - "id" : 380 + "id" : 430 }, { "id" : -213 @@ -3378,7 +3295,7 @@ "damage" : 6 }, { - "id" : 425 + "id" : 542 }, { "id" : 25 @@ -3387,46 +3304,46 @@ "id" : 84 }, { - "id" : 500 + "id" : 524 }, { - "id" : 501 + "id" : 525 }, { - "id" : 502 + "id" : 526 }, { - "id" : 503 + "id" : 527 }, { - "id" : 504 + "id" : 528 }, { - "id" : 505 + "id" : 529 }, { - "id" : 506 + "id" : 530 }, { - "id" : 507 + "id" : 531 }, { - "id" : 508 + "id" : 532 }, { - "id" : 509 + "id" : 533 }, { - "id" : 510 + "id" : 534 }, { - "id" : 511 + "id" : 535 }, { - "id" : 759 + "id" : 608 }, { - "id" : 348 + "id" : 392 }, { "id" : 89 @@ -3438,96 +3355,89 @@ "id" : 169 }, { - "id" : 323 + "id" : 358 }, { - "id" : 472 + "id" : 566 }, { - "id" : 473 + "id" : 567 }, { - "id" : 474 + "id" : 568 }, { - "id" : 475 + "id" : 569 }, { - "id" : 476 + "id" : 570 }, { - "id" : 753 + "id" : 602 }, { - "id" : 754 + "id" : 603 + }, + { + "id" : 357 + }, + { + "id" : 503 + }, + { + "id" : 581 + }, + { + "id" : 504 }, { "id" : 321 }, { - "id" : 389 + "id" : 360 }, { - "id" : 737 + "id" : 361 }, { - "id" : 390 + "id" : 362 }, { - "id" : 281 + "id" : 363 }, { - "id" : 325 + "id" : 364 }, { - "id" : 325, - "damage" : 1 + "id" : 365 }, { - "id" : 325, - "damage" : 8 + "id" : 366 }, { - "id" : 325, - "damage" : 10 + "id" : 367 }, { - "id" : 325, - "damage" : 2 - }, - { - "id" : 325, + "id" : 506, "damage" : 3 }, { - "id" : 325, - "damage" : 4 - }, - { - "id" : 325, - "damage" : 5 - }, - { - "id" : 397, - "damage" : 3 - }, - { - "id" : 397, + "id" : 506, "damage" : 2 }, { - "id" : 397, + "id" : 506, "damage" : 4 }, { - "id" : 397, + "id" : 506, "damage" : 5 }, { - "id" : 397 + "id" : 506 }, { - "id" : 397, + "id" : 506, "damage" : 1 }, { @@ -3546,92 +3456,576 @@ "id" : 120 }, { - "id" : 263 + "id" : 302 }, { - "id" : 263, - "damage" : 1 + "id" : 303 }, { - "id" : 264 + "id" : 304 }, { - "id" : 452 + "id" : 559 }, { - "id" : 265 + "id" : 305 }, { - "id" : 752 + "id" : 601 }, { - "id" : 742 + "id" : 591 }, { - "id" : 371 + "id" : 423 }, { - "id" : 266 + "id" : 306 }, { - "id" : 388 + "id" : 502 }, { - "id" : 406 + "id" : 514 }, { - "id" : 337 + "id" : 382 }, { - "id" : 336 + "id" : 381 }, { - "id" : 405 + "id" : 513 }, { - "id" : 409 + "id" : 555 + }, + { + "id" : 539 + }, + { + "id" : 560 + }, + { + "id" : 561 + }, + { + "id" : 562 + }, + { + "id" : 564 + }, + { + "id" : 326 + }, + { + "id" : 327 + }, + { + "id" : 356 + }, + { + "id" : 328 + }, + { + "id" : 379 + }, + { + "id" : 519 + }, + { + "id" : 518 + }, + { + "id" : 499 + }, + { + "id" : 421 + }, + { + "id" : 427 + }, + { + "id" : 428 + }, + { + "id" : 426 + }, + { + "id" : 550 + }, + { + "id" : 556 }, { "id" : 422 }, { - "id" : 465 + "id" : 386 }, { - "id" : 467 + "id" : 420 }, { - "id" : 468 + "id" : 431 }, { - "id" : 470 + "id" : 508 }, { - "id" : 287 + "id" : 208 }, { - "id" : 288 + "id" : 615 }, { - "id" : 318 - }, - { - "id" : 289 - }, - { - "id" : 334 - }, - { - "id" : 415 - }, - { - "id" : 414 + "id" : 384 }, { "id" : 385 }, { - "id" : 369 + "id" : 500 + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAQAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAQAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAQAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAQAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAQAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQFAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQFAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQFAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQGAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQGAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQGAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQHAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQHAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQHAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQIAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAQAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAUAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAQAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAUAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAQAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAUAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQMAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQMAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQNAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQNAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQOAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQOAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQOAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAQAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAUAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQQAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQRAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQRAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQRAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQSAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQSAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQSAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAQAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAUAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQUAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQUAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQVAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQWAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQXAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQXAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQXAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQYAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQYAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQYAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQZAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQZAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQaAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQbAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQcAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAQAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAUAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQeAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQeAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQeAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQfAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQfAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQfAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQgAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQhAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAQAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQjAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQjAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQjAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQkAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQkAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQkAAIDAGx2bAMAAAA=" + }, + { + "id" : 373 + }, + { + "id" : 376 + }, + { + "id" : 374 + }, + { + "id" : 375 }, { "id" : 377 @@ -3639,496 +4033,6 @@ { "id" : 378 }, - { - "id" : 376 - }, - { - "id" : 437 - }, - { - "id" : 445 - }, - { - "id" : 370 - }, - { - "id" : 341 - }, - { - "id" : 368 - }, - { - "id" : 381 - }, - { - "id" : 399 - }, - { - "id" : 208 - }, - { - "id" : 426 - }, - { - "id" : 339 - }, - { - "id" : 340 - }, - { - "id" : 386 - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZAAAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZAAAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZAAAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBAACAgBpZAAAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZAEAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZAEAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZAEAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBAACAgBpZAEAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZAIAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZAIAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZAIAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBAACAgBpZAIAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZAMAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZAMAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZAMAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBAACAgBpZAMAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZAQAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZAQAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZAQAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBAACAgBpZAQAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZAUAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZAUAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZAUAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZAYAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZAYAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZAYAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZAcAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZAcAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZAcAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZAgAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZAkAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZAkAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZAkAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBAACAgBpZAkAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBQACAgBpZAkAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZAoAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZAoAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZAoAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBAACAgBpZAoAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBQACAgBpZAoAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZAsAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZAsAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZAsAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBAACAgBpZAsAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBQACAgBpZAsAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZAwAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZAwAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZA0AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZA0AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZA4AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZA4AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZA4AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZA8AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZA8AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZA8AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBAACAgBpZA8AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBQACAgBpZA8AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZBAAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZBEAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZBEAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZBEAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZBIAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZBIAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZBIAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZBMAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZBMAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZBMAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBAACAgBpZBMAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBQACAgBpZBMAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZBQAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZBQAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZBUAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZBYAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZBcAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZBcAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZBcAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZBgAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZBgAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZBgAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZBkAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZBkAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZBoAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZBsAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZBwAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZB0AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZB0AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZB0AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBAACAgBpZB0AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBQACAgBpZB0AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZB4AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZB4AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZB4AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZB8AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZB8AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZB8AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZCAAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZCEAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZCIAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZCIAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZCIAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBAACAgBpZCIAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZCMAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZCMAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZCMAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZCQAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZCQAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZCQAAAA=" - }, - { - "id" : 333 - }, - { - "id" : 333, - "damage" : 1 - }, - { - "id" : 333, - "damage" : 2 - }, - { - "id" : 333, - "damage" : 3 - }, - { - "id" : 333, - "damage" : 4 - }, - { - "id" : 333, - "damage" : 5 - }, { "id" : 66 }, @@ -4142,19 +4046,19 @@ "id" : 126 }, { - "id" : 328 + "id" : 368 }, { - "id" : 342 + "id" : 387 }, { - "id" : 408 + "id" : 516 }, { - "id" : 407 + "id" : 515 }, { - "id" : 331 + "id" : 371 }, { "id" : 152 @@ -4241,13 +4145,13 @@ "id" : 151 }, { - "id" : 356 + "id" : 417 }, { - "id" : 404 + "id" : 512 }, { - "id" : 410 + "id" : 517 }, { "id" : 125, @@ -4269,252 +4173,255 @@ "id" : 46 }, { - "id" : 421 + "id" : 538 }, { "id" : -204 }, { - "id" : 446 + "id" : 557 }, { - "id" : 446, + "id" : 557, "damage" : 8 }, { - "id" : 446, + "id" : 557, "damage" : 7 }, { - "id" : 446, + "id" : 557, "damage" : 15 }, { - "id" : 446, + "id" : 557, "damage" : 12 }, { - "id" : 446, + "id" : 557, "damage" : 14 }, { - "id" : 446, + "id" : 557, "damage" : 1 }, { - "id" : 446, + "id" : 557, "damage" : 4 }, { - "id" : 446, + "id" : 557, "damage" : 5 }, { - "id" : 446, + "id" : 557, "damage" : 13 }, { - "id" : 446, + "id" : 557, "damage" : 9 }, { - "id" : 446, + "id" : 557, "damage" : 3 }, { - "id" : 446, + "id" : 557, "damage" : 11 }, { - "id" : 446, + "id" : 557, "damage" : 10 }, { - "id" : 446, + "id" : 557, "damage" : 2 }, { - "id" : 446, + "id" : 557, "damage" : 6 }, { - "id" : 446, + "id" : 557, "damage" : 15, "nbt_b64" : "CgAAAwQAVHlwZQEAAAAA" }, { - "id" : 434 + "id" : 572 }, { - "id" : 434, - "damage" : 1 + "id" : 573 }, { - "id" : 434, - "damage" : 2 + "id" : 571 }, { - "id" : 434, - "damage" : 3 + "id" : 574 }, { - "id" : 434, - "damage" : 4 + "id" : 575 }, { - "id" : 434, - "damage" : 5 + "id" : 576 }, { - "id" : 434, - "damage" : 6 + "id" : 577 }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMAAAAAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwAAAAAAAQYARmxpZ2h0AQAA" }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMKAQAAAAcNAEZpcmV3b3JrQ29sb3IBAAAAAAEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAABwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMKAQAAAAcNAEZpcmV3b3JrQ29sb3IBAAAACAEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAIBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMKAQAAAAcNAEZpcmV3b3JrQ29sb3IBAAAABwEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAHBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMKAQAAAAcNAEZpcmV3b3JrQ29sb3IBAAAADwEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAPBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMKAQAAAAcNAEZpcmV3b3JrQ29sb3IBAAAADAEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAMBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMKAQAAAAcNAEZpcmV3b3JrQ29sb3IBAAAADgEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAOBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMKAQAAAAcNAEZpcmV3b3JrQ29sb3IBAAAAAQEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAABBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMKAQAAAAcNAEZpcmV3b3JrQ29sb3IBAAAABAEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAEBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMKAQAAAAcNAEZpcmV3b3JrQ29sb3IBAAAABQEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAFBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMKAQAAAAcNAEZpcmV3b3JrQ29sb3IBAAAADQEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAANBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMKAQAAAAcNAEZpcmV3b3JrQ29sb3IBAAAACQEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAJBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMKAQAAAAcNAEZpcmV3b3JrQ29sb3IBAAAAAwEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAADBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMKAQAAAAcNAEZpcmV3b3JrQ29sb3IBAAAACwEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAALBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMKAQAAAAcNAEZpcmV3b3JrQ29sb3IBAAAACgEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAKBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMKAQAAAAcNAEZpcmV3b3JrQ29sb3IBAAAAAgEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAACBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMKAQAAAAcNAEZpcmV3b3JrQ29sb3IBAAAABgEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAGBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" }, { - "id" : 402, - "nbt_b64" : "CgAAAwsAY3VzdG9tQ29sb3IhHR3/Cg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAAEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAA=" + "id" : 510, + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yIR0d/wA=" }, { - "id" : 402, + "id" : 510, "damage" : 8, - "nbt_b64" : "CgAAAwsAY3VzdG9tQ29sb3JST0f/Cg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACAEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAA=" + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yUk9H/wA=" }, { - "id" : 402, + "id" : 510, "damage" : 7, - "nbt_b64" : "CgAAAwsAY3VzdG9tQ29sb3KXnZ3/Cg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABwEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAA=" + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yl52d/wA=" }, { - "id" : 402, + "id" : 510, "damage" : 15, - "nbt_b64" : "CgAAAwsAY3VzdG9tQ29sb3Lw8PD/Cg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADwEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAA=" + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9y8PDw/wA=" }, { - "id" : 402, + "id" : 510, "damage" : 12, - "nbt_b64" : "CgAAAwsAY3VzdG9tQ29sb3Laszr/Cg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADAEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAA=" + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9y2rM6/wA=" }, { - "id" : 402, + "id" : 510, "damage" : 14, - "nbt_b64" : "CgAAAwsAY3VzdG9tQ29sb3IdgPn/Cg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADgEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAA=" + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yHYD5/wA=" }, { - "id" : 402, + "id" : 510, "damage" : 1, - "nbt_b64" : "CgAAAwsAY3VzdG9tQ29sb3ImLrD/Cg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAQEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAA=" + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yJi6w/wA=" }, { - "id" : 402, + "id" : 510, "damage" : 4, - "nbt_b64" : "CgAAAwsAY3VzdG9tQ29sb3KqRDz/Cg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABAEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAA=" + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yqkQ8/wA=" }, { - "id" : 402, + "id" : 510, "damage" : 5, - "nbt_b64" : "CgAAAwsAY3VzdG9tQ29sb3K4Mon/Cg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABQEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAA=" + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yuDKJ/wA=" }, { - "id" : 402, + "id" : 510, "damage" : 13, - "nbt_b64" : "CgAAAwsAY3VzdG9tQ29sb3K9Tsf/Cg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADQEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAA=" + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yvU7H/wA=" }, { - "id" : 402, + "id" : 510, "damage" : 9, - "nbt_b64" : "CgAAAwsAY3VzdG9tQ29sb3Kqi/P/Cg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACQEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAA=" + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yqovz/wA=" }, { - "id" : 402, + "id" : 510, "damage" : 3, - "nbt_b64" : "CgAAAwsAY3VzdG9tQ29sb3IyVIP/Cg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAwEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAA=" + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yMlSD/wA=" }, { - "id" : 402, + "id" : 510, "damage" : 11, - "nbt_b64" : "CgAAAwsAY3VzdG9tQ29sb3I92P7/Cg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACwEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAA=" + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yPdj+/wA=" }, { - "id" : 402, + "id" : 510, "damage" : 10, - "nbt_b64" : "CgAAAwsAY3VzdG9tQ29sb3Ifx4D/Cg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACgEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAA=" + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yH8eA/wA=" }, { - "id" : 402, + "id" : 510, "damage" : 2, - "nbt_b64" : "CgAAAwsAY3VzdG9tQ29sb3IWfF7/Cg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAgEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAA=" + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yFnxe/wA=" }, { - "id" : 402, + "id" : 510, "damage" : 6, - "nbt_b64" : "CgAAAwsAY3VzdG9tQ29sb3KcnBb/Cg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABgEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAA=" + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9ynJwW/wA=" + }, + { + "id" : 607 + }, + { + "id" : -239 + }, + { + "id" : 590 } ] } \ No newline at end of file diff --git a/connector/src/main/resources/bedrock/legacy_block_ids.json b/connector/src/main/resources/bedrock/legacy_block_ids.json deleted file mode 100644 index 27594915..00000000 --- a/connector/src/main/resources/bedrock/legacy_block_ids.json +++ /dev/null @@ -1,555 +0,0 @@ -{ - "minecraft:air" : 0, - "minecraft:stone" : 1, - "minecraft:grass" : 2, - "minecraft:dirt" : 3, - "minecraft:cobblestone" : 4, - "minecraft:planks" : 5, - "minecraft:sapling" : 6, - "minecraft:bedrock" : 7, - "minecraft:flowing_water" : 8, - "minecraft:water" : 9, - "minecraft:flowing_lava" : 10, - "minecraft:lava" : 11, - "minecraft:sand" : 12, - "minecraft:gravel" : 13, - "minecraft:gold_ore" : 14, - "minecraft:iron_ore" : 15, - "minecraft:coal_ore" : 16, - "minecraft:log" : 17, - "minecraft:leaves" : 18, - "minecraft:sponge" : 19, - "minecraft:glass" : 20, - "minecraft:lapis_ore" : 21, - "minecraft:lapis_block" : 22, - "minecraft:dispenser" : 23, - "minecraft:sandstone" : 24, - "minecraft:noteblock" : 25, - "minecraft:bed" : 26, - "minecraft:golden_rail" : 27, - "minecraft:detector_rail" : 28, - "minecraft:sticky_piston" : 29, - "minecraft:web" : 30, - "minecraft:tallgrass" : 31, - "minecraft:deadbush" : 32, - "minecraft:piston" : 33, - "minecraft:pistonArmCollision" : 34, - "minecraft:wool" : 35, - "minecraft:element_0" : 36, - "minecraft:yellow_flower" : 37, - "minecraft:red_flower" : 38, - "minecraft:brown_mushroom" : 39, - "minecraft:red_mushroom" : 40, - "minecraft:gold_block" : 41, - "minecraft:iron_block" : 42, - "minecraft:double_stone_slab" : 43, - "minecraft:stone_slab" : 44, - "minecraft:brick_block" : 45, - "minecraft:tnt" : 46, - "minecraft:bookshelf" : 47, - "minecraft:mossy_cobblestone" : 48, - "minecraft:obsidian" : 49, - "minecraft:torch" : 50, - "minecraft:fire" : 51, - "minecraft:mob_spawner" : 52, - "minecraft:oak_stairs" : 53, - "minecraft:chest" : 54, - "minecraft:redstone_wire" : 55, - "minecraft:diamond_ore" : 56, - "minecraft:diamond_block" : 57, - "minecraft:crafting_table" : 58, - "minecraft:wheat" : 59, - "minecraft:farmland" : 60, - "minecraft:furnace" : 61, - "minecraft:lit_furnace" : 62, - "minecraft:standing_sign" : 63, - "minecraft:wooden_door" : 64, - "minecraft:ladder" : 65, - "minecraft:rail" : 66, - "minecraft:stone_stairs" : 67, - "minecraft:wall_sign" : 68, - "minecraft:lever" : 69, - "minecraft:stone_pressure_plate" : 70, - "minecraft:iron_door" : 71, - "minecraft:wooden_pressure_plate" : 72, - "minecraft:redstone_ore" : 73, - "minecraft:lit_redstone_ore" : 74, - "minecraft:unlit_redstone_torch" : 75, - "minecraft:redstone_torch" : 76, - "minecraft:stone_button" : 77, - "minecraft:snow_layer" : 78, - "minecraft:ice" : 79, - "minecraft:snow" : 80, - "minecraft:cactus" : 81, - "minecraft:clay" : 82, - "minecraft:reeds" : 83, - "minecraft:jukebox" : 84, - "minecraft:fence" : 85, - "minecraft:pumpkin" : 86, - "minecraft:netherrack" : 87, - "minecraft:soul_sand" : 88, - "minecraft:glowstone" : 89, - "minecraft:portal" : 90, - "minecraft:lit_pumpkin" : 91, - "minecraft:cake" : 92, - "minecraft:unpowered_repeater" : 93, - "minecraft:powered_repeater" : 94, - "minecraft:invisibleBedrock" : 95, - "minecraft:trapdoor" : 96, - "minecraft:monster_egg" : 97, - "minecraft:stonebrick" : 98, - "minecraft:brown_mushroom_block" : 99, - "minecraft:red_mushroom_block" : 100, - "minecraft:iron_bars" : 101, - "minecraft:glass_pane" : 102, - "minecraft:melon_block" : 103, - "minecraft:pumpkin_stem" : 104, - "minecraft:melon_stem" : 105, - "minecraft:vine" : 106, - "minecraft:fence_gate" : 107, - "minecraft:brick_stairs" : 108, - "minecraft:stone_brick_stairs" : 109, - "minecraft:mycelium" : 110, - "minecraft:waterlily" : 111, - "minecraft:nether_brick" : 112, - "minecraft:nether_brick_fence" : 113, - "minecraft:nether_brick_stairs" : 114, - "minecraft:nether_wart" : 115, - "minecraft:enchanting_table" : 116, - "minecraft:brewing_stand" : 117, - "minecraft:cauldron" : 118, - "minecraft:end_portal" : 119, - "minecraft:end_portal_frame" : 120, - "minecraft:end_stone" : 121, - "minecraft:dragon_egg" : 122, - "minecraft:redstone_lamp" : 123, - "minecraft:lit_redstone_lamp" : 124, - "minecraft:dropper" : 125, - "minecraft:activator_rail" : 126, - "minecraft:cocoa" : 127, - "minecraft:sandstone_stairs" : 128, - "minecraft:emerald_ore" : 129, - "minecraft:ender_chest" : 130, - "minecraft:tripwire_hook" : 131, - "minecraft:tripWire" : 132, - "minecraft:emerald_block" : 133, - "minecraft:spruce_stairs" : 134, - "minecraft:birch_stairs" : 135, - "minecraft:jungle_stairs" : 136, - "minecraft:command_block" : 137, - "minecraft:beacon" : 138, - "minecraft:cobblestone_wall" : 139, - "minecraft:flower_pot" : 140, - "minecraft:carrots" : 141, - "minecraft:potatoes" : 142, - "minecraft:wooden_button" : 143, - "minecraft:skull" : 144, - "minecraft:anvil" : 145, - "minecraft:trapped_chest" : 146, - "minecraft:light_weighted_pressure_plate" : 147, - "minecraft:heavy_weighted_pressure_plate" : 148, - "minecraft:unpowered_comparator" : 149, - "minecraft:powered_comparator" : 150, - "minecraft:daylight_detector" : 151, - "minecraft:redstone_block" : 152, - "minecraft:quartz_ore" : 153, - "minecraft:hopper" : 154, - "minecraft:quartz_block" : 155, - "minecraft:quartz_stairs" : 156, - "minecraft:double_wooden_slab" : 157, - "minecraft:wooden_slab" : 158, - "minecraft:stained_hardened_clay" : 159, - "minecraft:stained_glass_pane" : 160, - "minecraft:leaves2" : 161, - "minecraft:log2" : 162, - "minecraft:acacia_stairs" : 163, - "minecraft:dark_oak_stairs" : 164, - "minecraft:slime" : 165, - "minecraft:iron_trapdoor" : 167, - "minecraft:prismarine" : 168, - "minecraft:seaLantern" : 169, - "minecraft:hay_block" : 170, - "minecraft:carpet" : 171, - "minecraft:hardened_clay" : 172, - "minecraft:coal_block" : 173, - "minecraft:packed_ice" : 174, - "minecraft:double_plant" : 175, - "minecraft:standing_banner" : 176, - "minecraft:wall_banner" : 177, - "minecraft:daylight_detector_inverted" : 178, - "minecraft:red_sandstone" : 179, - "minecraft:red_sandstone_stairs" : 180, - "minecraft:double_stone_slab2" : 181, - "minecraft:stone_slab2" : 182, - "minecraft:spruce_fence_gate" : 183, - "minecraft:birch_fence_gate" : 184, - "minecraft:jungle_fence_gate" : 185, - "minecraft:dark_oak_fence_gate" : 186, - "minecraft:acacia_fence_gate" : 187, - "minecraft:repeating_command_block" : 188, - "minecraft:chain_command_block" : 189, - "minecraft:hard_glass_pane" : 190, - "minecraft:hard_stained_glass_pane" : 191, - "minecraft:chemical_heat" : 192, - "minecraft:spruce_door" : 193, - "minecraft:birch_door" : 194, - "minecraft:jungle_door" : 195, - "minecraft:acacia_door" : 196, - "minecraft:dark_oak_door" : 197, - "minecraft:grass_path" : 198, - "minecraft:frame" : 199, - "minecraft:chorus_flower" : 200, - "minecraft:purpur_block" : 201, - "minecraft:colored_torch_rg" : 202, - "minecraft:purpur_stairs" : 203, - "minecraft:colored_torch_bp" : 204, - "minecraft:undyed_shulker_box" : 205, - "minecraft:end_bricks" : 206, - "minecraft:frosted_ice" : 207, - "minecraft:end_rod" : 208, - "minecraft:end_gateway" : 209, - "minecraft:allow" : 210, - "minecraft:deny" : 211, - "minecraft:border_block" : 212, - "minecraft:magma" : 213, - "minecraft:nether_wart_block" : 214, - "minecraft:red_nether_brick" : 215, - "minecraft:bone_block" : 216, - "minecraft:structure_void" : 217, - "minecraft:shulker_box" : 218, - "minecraft:purple_glazed_terracotta" : 219, - "minecraft:white_glazed_terracotta" : 220, - "minecraft:orange_glazed_terracotta" : 221, - "minecraft:magenta_glazed_terracotta" : 222, - "minecraft:light_blue_glazed_terracotta" : 223, - "minecraft:yellow_glazed_terracotta" : 224, - "minecraft:lime_glazed_terracotta" : 225, - "minecraft:pink_glazed_terracotta" : 226, - "minecraft:gray_glazed_terracotta" : 227, - "minecraft:silver_glazed_terracotta" : 228, - "minecraft:cyan_glazed_terracotta" : 229, - "minecraft:blue_glazed_terracotta" : 231, - "minecraft:brown_glazed_terracotta" : 232, - "minecraft:green_glazed_terracotta" : 233, - "minecraft:red_glazed_terracotta" : 234, - "minecraft:black_glazed_terracotta" : 235, - "minecraft:concrete" : 236, - "minecraft:concretePowder" : 237, - "minecraft:chemistry_table" : 238, - "minecraft:underwater_torch" : 239, - "minecraft:chorus_plant" : 240, - "minecraft:stained_glass" : 241, - "minecraft:camera" : 242, - "minecraft:podzol" : 243, - "minecraft:beetroot" : 244, - "minecraft:stonecutter" : 245, - "minecraft:glowingobsidian" : 246, - "minecraft:netherreactor" : 247, - "minecraft:info_update" : 248, - "minecraft:info_update2" : 249, - "minecraft:movingBlock" : 250, - "minecraft:observer" : 251, - "minecraft:structure_block" : 252, - "minecraft:hard_glass" : 253, - "minecraft:hard_stained_glass" : 254, - "minecraft:reserved6" : 255, - "minecraft:prismarine_stairs" : 257, - "minecraft:dark_prismarine_stairs" : 258, - "minecraft:prismarine_bricks_stairs" : 259, - "minecraft:stripped_spruce_log" : 260, - "minecraft:stripped_birch_log" : 261, - "minecraft:stripped_jungle_log" : 262, - "minecraft:stripped_acacia_log" : 263, - "minecraft:stripped_dark_oak_log" : 264, - "minecraft:stripped_oak_log" : 265, - "minecraft:blue_ice" : 266, - "minecraft:element_1" : 267, - "minecraft:element_2" : 268, - "minecraft:element_3" : 269, - "minecraft:element_4" : 270, - "minecraft:element_5" : 271, - "minecraft:element_6" : 272, - "minecraft:element_7" : 273, - "minecraft:element_8" : 274, - "minecraft:element_9" : 275, - "minecraft:element_10" : 276, - "minecraft:element_11" : 277, - "minecraft:element_12" : 278, - "minecraft:element_13" : 279, - "minecraft:element_14" : 280, - "minecraft:element_15" : 281, - "minecraft:element_16" : 282, - "minecraft:element_17" : 283, - "minecraft:element_18" : 284, - "minecraft:element_19" : 285, - "minecraft:element_20" : 286, - "minecraft:element_21" : 287, - "minecraft:element_22" : 288, - "minecraft:element_23" : 289, - "minecraft:element_24" : 290, - "minecraft:element_25" : 291, - "minecraft:element_26" : 292, - "minecraft:element_27" : 293, - "minecraft:element_28" : 294, - "minecraft:element_29" : 295, - "minecraft:element_30" : 296, - "minecraft:element_31" : 297, - "minecraft:element_32" : 298, - "minecraft:element_33" : 299, - "minecraft:element_34" : 300, - "minecraft:element_35" : 301, - "minecraft:element_36" : 302, - "minecraft:element_37" : 303, - "minecraft:element_38" : 304, - "minecraft:element_39" : 305, - "minecraft:element_40" : 306, - "minecraft:element_41" : 307, - "minecraft:element_42" : 308, - "minecraft:element_43" : 309, - "minecraft:element_44" : 310, - "minecraft:element_45" : 311, - "minecraft:element_46" : 312, - "minecraft:element_47" : 313, - "minecraft:element_48" : 314, - "minecraft:element_49" : 315, - "minecraft:element_50" : 316, - "minecraft:element_51" : 317, - "minecraft:element_52" : 318, - "minecraft:element_53" : 319, - "minecraft:element_54" : 320, - "minecraft:element_55" : 321, - "minecraft:element_56" : 322, - "minecraft:element_57" : 323, - "minecraft:element_58" : 324, - "minecraft:element_59" : 325, - "minecraft:element_60" : 326, - "minecraft:element_61" : 327, - "minecraft:element_62" : 328, - "minecraft:element_63" : 329, - "minecraft:element_64" : 330, - "minecraft:element_65" : 331, - "minecraft:element_66" : 332, - "minecraft:element_67" : 333, - "minecraft:element_68" : 334, - "minecraft:element_69" : 335, - "minecraft:element_70" : 336, - "minecraft:element_71" : 337, - "minecraft:element_72" : 338, - "minecraft:element_73" : 339, - "minecraft:element_74" : 340, - "minecraft:element_75" : 341, - "minecraft:element_76" : 342, - "minecraft:element_77" : 343, - "minecraft:element_78" : 344, - "minecraft:element_79" : 345, - "minecraft:element_80" : 346, - "minecraft:element_81" : 347, - "minecraft:element_82" : 348, - "minecraft:element_83" : 349, - "minecraft:element_84" : 350, - "minecraft:element_85" : 351, - "minecraft:element_86" : 352, - "minecraft:element_87" : 353, - "minecraft:element_88" : 354, - "minecraft:element_89" : 355, - "minecraft:element_90" : 356, - "minecraft:element_91" : 357, - "minecraft:element_92" : 358, - "minecraft:element_93" : 359, - "minecraft:element_94" : 360, - "minecraft:element_95" : 361, - "minecraft:element_96" : 362, - "minecraft:element_97" : 363, - "minecraft:element_98" : 364, - "minecraft:element_99" : 365, - "minecraft:element_100" : 366, - "minecraft:element_101" : 367, - "minecraft:element_102" : 368, - "minecraft:element_103" : 369, - "minecraft:element_104" : 370, - "minecraft:element_105" : 371, - "minecraft:element_106" : 372, - "minecraft:element_107" : 373, - "minecraft:element_108" : 374, - "minecraft:element_109" : 375, - "minecraft:element_110" : 376, - "minecraft:element_111" : 377, - "minecraft:element_112" : 378, - "minecraft:element_113" : 379, - "minecraft:element_114" : 380, - "minecraft:element_115" : 381, - "minecraft:element_116" : 382, - "minecraft:element_117" : 383, - "minecraft:element_118" : 384, - "minecraft:seagrass" : 385, - "minecraft:coral" : 386, - "minecraft:coral_block" : 387, - "minecraft:coral_fan" : 388, - "minecraft:coral_fan_dead" : 389, - "minecraft:coral_fan_hang" : 390, - "minecraft:coral_fan_hang2" : 391, - "minecraft:coral_fan_hang3" : 392, - "minecraft:kelp" : 393, - "minecraft:dried_kelp_block" : 394, - "minecraft:acacia_button" : 395, - "minecraft:birch_button" : 396, - "minecraft:dark_oak_button" : 397, - "minecraft:jungle_button" : 398, - "minecraft:spruce_button" : 399, - "minecraft:acacia_trapdoor" : 400, - "minecraft:birch_trapdoor" : 401, - "minecraft:dark_oak_trapdoor" : 402, - "minecraft:jungle_trapdoor" : 403, - "minecraft:spruce_trapdoor" : 404, - "minecraft:acacia_pressure_plate" : 405, - "minecraft:birch_pressure_plate" : 406, - "minecraft:dark_oak_pressure_plate" : 407, - "minecraft:jungle_pressure_plate" : 408, - "minecraft:spruce_pressure_plate" : 409, - "minecraft:carved_pumpkin" : 410, - "minecraft:sea_pickle" : 411, - "minecraft:conduit" : 412, - "minecraft:turtle_egg" : 414, - "minecraft:bubble_column" : 415, - "minecraft:barrier" : 416, - "minecraft:stone_slab3" : 417, - "minecraft:bamboo" : 418, - "minecraft:bamboo_sapling" : 419, - "minecraft:scaffolding" : 420, - "minecraft:stone_slab4" : 421, - "minecraft:double_stone_slab3" : 422, - "minecraft:double_stone_slab4" : 423, - "minecraft:granite_stairs" : 424, - "minecraft:diorite_stairs" : 425, - "minecraft:andesite_stairs" : 426, - "minecraft:polished_granite_stairs" : 427, - "minecraft:polished_diorite_stairs" : 428, - "minecraft:polished_andesite_stairs" : 429, - "minecraft:mossy_stone_brick_stairs" : 430, - "minecraft:smooth_red_sandstone_stairs" : 431, - "minecraft:smooth_sandstone_stairs" : 432, - "minecraft:end_brick_stairs" : 433, - "minecraft:mossy_cobblestone_stairs" : 434, - "minecraft:normal_stone_stairs" : 435, - "minecraft:spruce_standing_sign" : 436, - "minecraft:spruce_wall_sign" : 437, - "minecraft:smooth_stone" : 438, - "minecraft:red_nether_brick_stairs" : 439, - "minecraft:smooth_quartz_stairs" : 440, - "minecraft:birch_standing_sign" : 441, - "minecraft:birch_wall_sign" : 442, - "minecraft:jungle_standing_sign" : 443, - "minecraft:jungle_wall_sign" : 444, - "minecraft:acacia_standing_sign" : 445, - "minecraft:acacia_wall_sign" : 446, - "minecraft:darkoak_standing_sign" : 447, - "minecraft:darkoak_wall_sign" : 448, - "minecraft:lectern" : 449, - "minecraft:grindstone" : 450, - "minecraft:blast_furnace" : 451, - "minecraft:stonecutter_block" : 452, - "minecraft:smoker" : 453, - "minecraft:lit_smoker" : 454, - "minecraft:cartography_table" : 455, - "minecraft:fletching_table" : 456, - "minecraft:smithing_table" : 457, - "minecraft:barrel" : 458, - "minecraft:loom" : 459, - "minecraft:bell" : 461, - "minecraft:sweet_berry_bush" : 462, - "minecraft:lantern" : 463, - "minecraft:campfire" : 464, - "minecraft:lava_cauldron" : 465, - "minecraft:jigsaw" : 466, - "minecraft:wood" : 467, - "minecraft:composter" : 468, - "minecraft:lit_blast_furnace" : 469, - "minecraft:light_block" : 470, - "minecraft:wither_rose" : 471, - "minecraft:stickyPistonArmCollision" : 472, - "minecraft:bee_nest" : 473, - "minecraft:beehive" : 474, - "minecraft:honey_block" : 475, - "minecraft:honeycomb_block" : 476, - "minecraft:lodestone" : 477, - "minecraft:crimson_roots" : 478, - "minecraft:warped_roots" : 479, - "minecraft:crimson_stem" : 480, - "minecraft:warped_stem" : 481, - "minecraft:warped_wart_block" : 482, - "minecraft:crimson_fungus" : 483, - "minecraft:warped_fungus" : 484, - "minecraft:shroomlight" : 485, - "minecraft:weeping_vines" : 486, - "minecraft:crimson_nylium" : 487, - "minecraft:warped_nylium" : 488, - "minecraft:basalt" : 489, - "minecraft:polished_basalt" : 490, - "minecraft:soul_soil" : 491, - "minecraft:soul_fire" : 492, - "minecraft:nether_sprouts" : 493, - "minecraft:target" : 494, - "minecraft:stripped_crimson_stem" : 495, - "minecraft:stripped_warped_stem" : 496, - "minecraft:crimson_planks" : 497, - "minecraft:warped_planks" : 498, - "minecraft:crimson_door" : 499, - "minecraft:warped_door" : 500, - "minecraft:crimson_trapdoor" : 501, - "minecraft:warped_trapdoor" : 502, - "minecraft:crimson_standing_sign" : 505, - "minecraft:warped_standing_sign" : 506, - "minecraft:crimson_wall_sign" : 507, - "minecraft:warped_wall_sign" : 508, - "minecraft:crimson_stairs" : 509, - "minecraft:warped_stairs" : 510, - "minecraft:crimson_fence" : 511, - "minecraft:warped_fence" : 512, - "minecraft:crimson_fence_gate" : 513, - "minecraft:warped_fence_gate" : 514, - "minecraft:crimson_button" : 515, - "minecraft:warped_button" : 516, - "minecraft:crimson_pressure_plate" : 517, - "minecraft:warped_pressure_plate" : 518, - "minecraft:crimson_slab" : 519, - "minecraft:warped_slab" : 520, - "minecraft:crimson_double_slab" : 521, - "minecraft:warped_double_slab" : 522, - "minecraft:soul_torch" : 523, - "minecraft:soul_lantern" : 524, - "minecraft:netherite_block" : 525, - "minecraft:ancient_debris" : 526, - "minecraft:respawn_anchor" : 527, - "minecraft:blackstone" : 528, - "minecraft:polished_blackstone_bricks" : 529, - "minecraft:polished_blackstone_brick_stairs" : 530, - "minecraft:blackstone_stairs" : 531, - "minecraft:blackstone_wall" : 532, - "minecraft:polished_blackstone_brick_wall" : 533, - "minecraft:chiseled_polished_blackstone" : 534, - "minecraft:cracked_polished_blackstone_bricks" : 535, - "minecraft:gilded_blackstone" : 536, - "minecraft:blackstone_slab" : 537, - "minecraft:blackstone_double_slab" : 538, - "minecraft:polished_blackstone_brick_slab" : 539, - "minecraft:polished_blackstone_brick_double_slab" : 540, - "minecraft:chain" : 541, - "minecraft:twisting_vines" : 542, - "minecraft:nether_gold_ore" : 543, - "minecraft:crying_obsidian" : 544, - "minecraft:soul_campfire" : 545, - "minecraft:polished_blackstone" : 546, - "minecraft:polished_blackstone_stairs" : 547, - "minecraft:polished_blackstone_slab" : 548, - "minecraft:polished_blackstone_double_slab" : 549, - "minecraft:polished_blackstone_pressure_plate" : 550, - "minecraft:polished_blackstone_button" : 551, - "minecraft:polished_blackstone_wall" : 552, - "minecraft:warped_hyphae" : 553, - "minecraft:crimson_hyphae" : 554, - "minecraft:stripped_crimson_hyphae" : 555, - "minecraft:stripped_warped_hyphae" : 556, - "minecraft:chiseled_nether_bricks" : 557, - "minecraft:cracked_nether_bricks" : 558, - "minecraft:quartz_bricks" : 559 -} \ No newline at end of file diff --git a/connector/src/main/resources/bedrock/legacy_item_ids.json b/connector/src/main/resources/bedrock/legacy_item_ids.json deleted file mode 100644 index 3f22c91c..00000000 --- a/connector/src/main/resources/bedrock/legacy_item_ids.json +++ /dev/null @@ -1,255 +0,0 @@ -{ - "minecraft:iron_shovel" : 256, - "minecraft:iron_pickaxe" : 257, - "minecraft:iron_axe" : 258, - "minecraft:flint_and_steel" : 259, - "minecraft:apple" : 260, - "minecraft:bow" : 261, - "minecraft:arrow" : 262, - "minecraft:coal" : 263, - "minecraft:diamond" : 264, - "minecraft:iron_ingot" : 265, - "minecraft:gold_ingot" : 266, - "minecraft:iron_sword" : 267, - "minecraft:wooden_sword" : 268, - "minecraft:wooden_shovel" : 269, - "minecraft:wooden_pickaxe" : 270, - "minecraft:wooden_axe" : 271, - "minecraft:stone_sword" : 272, - "minecraft:stone_shovel" : 273, - "minecraft:stone_pickaxe" : 274, - "minecraft:stone_axe" : 275, - "minecraft:diamond_sword" : 276, - "minecraft:diamond_shovel" : 277, - "minecraft:diamond_pickaxe" : 278, - "minecraft:diamond_axe" : 279, - "minecraft:stick" : 280, - "minecraft:bowl" : 281, - "minecraft:mushroom_stew" : 282, - "minecraft:golden_sword" : 283, - "minecraft:golden_shovel" : 284, - "minecraft:golden_pickaxe" : 285, - "minecraft:golden_axe" : 286, - "minecraft:string" : 287, - "minecraft:feather" : 288, - "minecraft:gunpowder" : 289, - "minecraft:wooden_hoe" : 290, - "minecraft:stone_hoe" : 291, - "minecraft:iron_hoe" : 292, - "minecraft:diamond_hoe" : 293, - "minecraft:golden_hoe" : 294, - "minecraft:wheat_seeds" : 295, - "minecraft:wheat" : 296, - "minecraft:bread" : 297, - "minecraft:leather_helmet" : 298, - "minecraft:leather_chestplate" : 299, - "minecraft:leather_leggings" : 300, - "minecraft:leather_boots" : 301, - "minecraft:chainmail_helmet" : 302, - "minecraft:chainmail_chestplate" : 303, - "minecraft:chainmail_leggings" : 304, - "minecraft:chainmail_boots" : 305, - "minecraft:iron_helmet" : 306, - "minecraft:iron_chestplate" : 307, - "minecraft:iron_leggings" : 308, - "minecraft:iron_boots" : 309, - "minecraft:diamond_helmet" : 310, - "minecraft:diamond_chestplate" : 311, - "minecraft:diamond_leggings" : 312, - "minecraft:diamond_boots" : 313, - "minecraft:golden_helmet" : 314, - "minecraft:golden_chestplate" : 315, - "minecraft:golden_leggings" : 316, - "minecraft:golden_boots" : 317, - "minecraft:flint" : 318, - "minecraft:porkchop" : 319, - "minecraft:cooked_porkchop" : 320, - "minecraft:painting" : 321, - "minecraft:golden_apple" : 322, - "minecraft:sign" : 323, - "minecraft:wooden_door" : 324, - "minecraft:bucket" : 325, - "minecraft:minecart" : 328, - "minecraft:saddle" : 329, - "minecraft:iron_door" : 330, - "minecraft:redstone" : 331, - "minecraft:snowball" : 332, - "minecraft:boat" : 333, - "minecraft:leather" : 334, - "minecraft:kelp" : 335, - "minecraft:brick" : 336, - "minecraft:clay_ball" : 337, - "minecraft:reeds" : 338, - "minecraft:paper" : 339, - "minecraft:book" : 340, - "minecraft:slime_ball" : 341, - "minecraft:chest_minecart" : 342, - "minecraft:egg" : 344, - "minecraft:compass" : 345, - "minecraft:fishing_rod" : 346, - "minecraft:clock" : 347, - "minecraft:glowstone_dust" : 348, - "minecraft:fish" : 349, - "minecraft:cooked_fish" : 350, - "minecraft:dye" : 351, - "minecraft:bone" : 352, - "minecraft:sugar" : 353, - "minecraft:cake" : 354, - "minecraft:bed" : 355, - "minecraft:repeater" : 356, - "minecraft:cookie" : 357, - "minecraft:map" : 358, - "minecraft:shears" : 359, - "minecraft:melon" : 360, - "minecraft:pumpkin_seeds" : 361, - "minecraft:melon_seeds" : 362, - "minecraft:beef" : 363, - "minecraft:cooked_beef" : 364, - "minecraft:chicken" : 365, - "minecraft:cooked_chicken" : 366, - "minecraft:rotten_flesh" : 367, - "minecraft:ender_pearl" : 368, - "minecraft:blaze_rod" : 369, - "minecraft:ghast_tear" : 370, - "minecraft:gold_nugget" : 371, - "minecraft:nether_wart" : 372, - "minecraft:potion" : 373, - "minecraft:glass_bottle" : 374, - "minecraft:spider_eye" : 375, - "minecraft:fermented_spider_eye" : 376, - "minecraft:blaze_powder" : 377, - "minecraft:magma_cream" : 378, - "minecraft:brewing_stand" : 379, - "minecraft:cauldron" : 380, - "minecraft:ender_eye" : 381, - "minecraft:speckled_melon" : 382, - "minecraft:spawn_egg" : 383, - "minecraft:experience_bottle" : 384, - "minecraft:fireball" : 385, - "minecraft:writable_book" : 386, - "minecraft:written_book" : 387, - "minecraft:emerald" : 388, - "minecraft:frame" : 389, - "minecraft:flower_pot" : 390, - "minecraft:carrot" : 391, - "minecraft:potato" : 392, - "minecraft:baked_potato" : 393, - "minecraft:poisonous_potato" : 394, - "minecraft:emptymap" : 395, - "minecraft:golden_carrot" : 396, - "minecraft:skull" : 397, - "minecraft:carrotonastick" : 398, - "minecraft:netherstar" : 399, - "minecraft:pumpkin_pie" : 400, - "minecraft:fireworks" : 401, - "minecraft:fireworkscharge" : 402, - "minecraft:enchanted_book" : 403, - "minecraft:comparator" : 404, - "minecraft:netherbrick" : 405, - "minecraft:quartz" : 406, - "minecraft:tnt_minecart" : 407, - "minecraft:hopper_minecart" : 408, - "minecraft:prismarine_shard" : 409, - "minecraft:hopper" : 410, - "minecraft:rabbit" : 411, - "minecraft:cooked_rabbit" : 412, - "minecraft:rabbit_stew" : 413, - "minecraft:rabbit_foot" : 414, - "minecraft:rabbit_hide" : 415, - "minecraft:horsearmorleather" : 416, - "minecraft:horsearmoriron" : 417, - "minecraft:horsearmorgold" : 418, - "minecraft:horsearmordiamond" : 419, - "minecraft:lead" : 420, - "minecraft:name_tag" : 421, - "minecraft:prismarine_crystals" : 422, - "minecraft:muttonraw" : 423, - "minecraft:muttoncooked" : 424, - "minecraft:armor_stand" : 425, - "minecraft:end_crystal" : 426, - "minecraft:spruce_door" : 427, - "minecraft:birch_door" : 428, - "minecraft:jungle_door" : 429, - "minecraft:acacia_door" : 430, - "minecraft:dark_oak_door" : 431, - "minecraft:chorus_fruit" : 432, - "minecraft:chorus_fruit_popped" : 433, - "minecraft:banner_pattern" : 434, - "minecraft:dragon_breath" : 437, - "minecraft:splash_potion" : 438, - "minecraft:lingering_potion" : 441, - "minecraft:sparkler" : 442, - "minecraft:command_block_minecart" : 443, - "minecraft:elytra" : 444, - "minecraft:shulker_shell" : 445, - "minecraft:banner" : 446, - "minecraft:medicine" : 447, - "minecraft:balloon" : 448, - "minecraft:rapid_fertilizer" : 449, - "minecraft:totem" : 450, - "minecraft:bleach" : 451, - "minecraft:iron_nugget" : 452, - "minecraft:ice_bomb" : 453, - "minecraft:trident" : 455, - "minecraft:beetroot" : 457, - "minecraft:beetroot_seeds" : 458, - "minecraft:beetroot_soup" : 459, - "minecraft:salmon" : 460, - "minecraft:clownfish" : 461, - "minecraft:pufferfish" : 462, - "minecraft:cooked_salmon" : 463, - "minecraft:dried_kelp" : 464, - "minecraft:nautilus_shell" : 465, - "minecraft:appleenchanted" : 466, - "minecraft:heart_of_the_sea" : 467, - "minecraft:turtle_shell_piece" : 468, - "minecraft:turtle_helmet" : 469, - "minecraft:phantom_membrane" : 470, - "minecraft:crossbow" : 471, - "minecraft:spruce_sign" : 472, - "minecraft:birch_sign" : 473, - "minecraft:jungle_sign" : 474, - "minecraft:acacia_sign" : 475, - "minecraft:darkoak_sign" : 476, - "minecraft:sweet_berries" : 477, - "minecraft:camera" : 498, - "minecraft:compound" : 499, - "minecraft:record_13" : 500, - "minecraft:record_cat" : 501, - "minecraft:record_blocks" : 502, - "minecraft:record_chirp" : 503, - "minecraft:record_far" : 504, - "minecraft:record_mall" : 505, - "minecraft:record_mellohi" : 506, - "minecraft:record_stal" : 507, - "minecraft:record_strad" : 508, - "minecraft:record_ward" : 509, - "minecraft:record_11" : 510, - "minecraft:record_wait" : 511, - "minecraft:shield" : 513, - "minecraft:campfire" : 720, - "minecraft:suspicious_stew" : 734, - "minecraft:honeycomb" : 736, - "minecraft:honey_bottle" : 737, - "minecraft:lodestonecompass" : 741, - "minecraft:netherite_ingot" : 742, - "minecraft:netherite_sword" : 743, - "minecraft:netherite_shovel" : 744, - "minecraft:netherite_pickaxe" : 745, - "minecraft:netherite_axe" : 746, - "minecraft:netherite_hoe" : 747, - "minecraft:netherite_helmet" : 748, - "minecraft:netherite_chestplate" : 749, - "minecraft:netherite_leggings" : 750, - "minecraft:netherite_boots" : 751, - "minecraft:netherite_scrap" : 752, - "minecraft:crimson_sign" : 753, - "minecraft:warped_sign" : 754, - "minecraft:crimson_door" : 755, - "minecraft:warped_door" : 756, - "minecraft:warped_fungus_on_a_stick" : 757, - "minecraft:chain" : 758, - "minecraft:record_pigstep" : 759, - "minecraft:nether_sprouts" : 760, - "minecraft:soul_campfire" : 801 -} \ No newline at end of file diff --git a/connector/src/main/resources/bedrock/runtime_block_states.dat b/connector/src/main/resources/bedrock/runtime_block_states.dat deleted file mode 100644 index feeeda664bdf469af04e3758e1a584b76bb71f34..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1063028 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 daWF8y=mGU<3(8pke_4.0.0 org.geysermc geyser-parent - parent + 1.2.0-SNAPSHOT pom Geyser Allows for players from Minecraft Bedrock Edition to join Minecraft Java Edition servers. @@ -71,19 +71,6 @@ - - - releases - opencollab-releases - https://repo.opencollab.dev/maven-releases - - - snapshots - opencollab-snapshots - https://repo.opencollab.dev/maven-snapshots - - - org.projectlombok From 003a1bef03b6aa68dc37199d994bc4fe6628bc4e Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Tue, 17 Nov 2020 12:54:09 -0500 Subject: [PATCH 043/116] Revert artifactory changes preventing compilation (#1555) --- Jenkinsfile | 22 +--------------------- pom.xml | 13 +++++++++++++ 2 files changed, 14 insertions(+), 21 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 96b189f2..7dfdaf30 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -26,27 +26,7 @@ pipeline { } steps { - rtMavenDeployer( - id: "maven-deployer", - serverId: "opencollab-artifactory", - releaseRepo: "maven-releases", - snapshotRepo: "maven-snapshots" - ) - rtMavenResolver( - id: "maven-resolver", - serverId: "opencollab-artifactory", - releaseRepo: "release", - snapshotRepo: "snapshot" - ) - rtMavenRun( - pom: 'pom.xml', - goals: 'javadoc:jar source:jar install -DskipTests', - deployerId: "maven-deployer", - resolverId: "maven-resolver" - ) - rtPublishBuildInfo( - serverId: "opencollab-artifactory" - ) + sh 'mvn javadoc:jar source:jar deploy -DskipTests' } } } diff --git a/pom.xml b/pom.xml index 011b320f..1b544f9e 100644 --- a/pom.xml +++ b/pom.xml @@ -71,6 +71,19 @@ + + + releases + opencollab-releases + https://repo.opencollab.dev/maven-releases + + + snapshots + opencollab-snapshots + https://repo.opencollab.dev/maven-snapshots + + + org.projectlombok From 99558b61a604d7c4c6ba85ec2bce563cdeb9f23a Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Tue, 17 Nov 2020 13:28:08 -0500 Subject: [PATCH 044/116] Print reason for disconnect when outdated (#1556) --- .../org/geysermc/connector/network/UpstreamPacketHandler.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java index e871b7f7..3c4dc821 100644 --- a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java @@ -61,9 +61,11 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { if (packetCodec == null) { if (loginPacket.getProtocolVersion() > BedrockProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) { // Too early to determine session locale + session.getConnector().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.outdated.server", BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion())); session.disconnect(LanguageUtils.getLocaleStringLog("geyser.network.outdated.server", BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion())); return true; } else if (loginPacket.getProtocolVersion() < BedrockProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) { + session.getConnector().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.outdated.client", BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion())); session.disconnect(LanguageUtils.getLocaleStringLog("geyser.network.outdated.client", BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion())); return true; } From 199778faea74fc9b7928ef3711d99f31bc19af18 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Wed, 18 Nov 2020 01:10:49 -0500 Subject: [PATCH 045/116] Fix regressions from 1.16.100 (#1558) * Fix regressions from 1.16.100 - Update mappings to fix recipe regressions and item differences - Villager trading NBT now prefers the String identifier and not the integer ID * Fix lodestone compass breaking --- .../connector/entity/ItemFrameEntity.java | 4 +- .../network/translators/item/ItemEntry.java | 3 +- .../translators/item/ItemRegistry.java | 40 +++++++------------ .../translators/item/ItemTranslator.java | 5 +-- .../translators/item/ToolItemEntry.java | 4 +- .../item/translators/CompassTranslator.java | 4 +- .../translators/nbt/CrossbowTranslator.java | 6 +-- .../nbt/ShulkerBoxItemTranslator.java | 3 +- .../java/world/JavaTradeListTranslator.java | 2 +- connector/src/main/resources/mappings | 2 +- 10 files changed, 28 insertions(+), 45 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java b/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java index 62fe3afe..972fa8d0 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java @@ -98,14 +98,12 @@ public class ItemFrameEntity extends Entity { ItemEntry itemEntry = ItemRegistry.getItem((ItemStack) entityMetadata.getValue()); NbtMapBuilder builder = NbtMap.builder(); - String blockName = ItemRegistry.getBedrockIdentifier(itemEntry); - builder.putByte("Count", (byte) itemData.getCount()); if (itemData.getTag() != null) { builder.put("tag", itemData.getTag().toBuilder().build()); } builder.putShort("Damage", itemData.getDamage()); - builder.putString("Name", blockName); + builder.putString("Name", itemEntry.getBedrockIdentifier()); NbtMapBuilder tag = getDefaultTag().toBuilder(); tag.put("Item", builder.build()); tag.putFloat("ItemDropChance", 1.0f); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemEntry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemEntry.java index 19f83cd9..3fcae17d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemEntry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemEntry.java @@ -34,9 +34,10 @@ import lombok.ToString; @ToString public class ItemEntry { - public static ItemEntry AIR = new ItemEntry("minecraft:air", 0, 0, 0, false); + public static ItemEntry AIR = new ItemEntry("minecraft:air", "minecraft:air", 0, 0, 0, false); private final String javaIdentifier; + private final String bedrockIdentifier; private final int javaId; private final int bedrockId; private final int bedrockData; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java index 3e026885..a4a2fb75 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java @@ -103,6 +103,9 @@ public class ItemRegistry { TypeReference> itemEntriesType = new TypeReference>() { }; + // Used to get the Bedrock namespaced ID (in instances where there are small differences) + Int2ObjectMap bedrockIdToIdentifier = new Int2ObjectOpenHashMap<>(); + List itemEntries; try { itemEntries = GeyserConnector.JSON_MAPPER.readValue(stream, itemEntriesType); @@ -114,6 +117,7 @@ public class ItemRegistry { for (JsonNode entry : itemEntries) { ITEMS.add(new StartGamePacket.ItemEntry(entry.get("name").textValue(), (short) entry.get("id").intValue())); + bedrockIdToIdentifier.put(entry.get("id").intValue(), entry.get("name").textValue()); if (entry.get("name").textValue().equals("minecraft:lodestone_compass")) { lodestoneCompassId = entry.get("id").intValue(); } @@ -132,28 +136,29 @@ public class ItemRegistry { Iterator> iterator = items.fields(); while (iterator.hasNext()) { Map.Entry entry = iterator.next(); + int bedrockId = entry.getValue().get("bedrock_id").intValue(); + String bedrockIdentifier = bedrockIdToIdentifier.get(bedrockId); + if (bedrockIdentifier == null) { + throw new RuntimeException("Missing Bedrock ID in mappings!: " + bedrockId); + } if (entry.getValue().has("tool_type")) { if (entry.getValue().has("tool_tier")) { ITEM_ENTRIES.put(itemIndex, new ToolItemEntry( - entry.getKey(), itemIndex, - entry.getValue().get("bedrock_id").intValue(), + entry.getKey(), bedrockIdentifier, itemIndex, bedrockId, entry.getValue().get("bedrock_data").intValue(), entry.getValue().get("tool_type").textValue(), entry.getValue().get("tool_tier").textValue(), entry.getValue().get("is_block") != null && entry.getValue().get("is_block").booleanValue())); } else { ITEM_ENTRIES.put(itemIndex, new ToolItemEntry( - entry.getKey(), itemIndex, - entry.getValue().get("bedrock_id").intValue(), + entry.getKey(), bedrockIdentifier, itemIndex, bedrockId, entry.getValue().get("bedrock_data").intValue(), entry.getValue().get("tool_type").textValue(), - "", - entry.getValue().get("is_block").booleanValue())); + "", entry.getValue().get("is_block").booleanValue())); } } else { ITEM_ENTRIES.put(itemIndex, new ItemEntry( - entry.getKey(), itemIndex, - entry.getValue().get("bedrock_id").intValue(), + entry.getKey(), bedrockIdentifier, itemIndex, bedrockId, entry.getValue().get("bedrock_data").intValue(), entry.getValue().get("is_block") != null && entry.getValue().get("is_block").booleanValue())); } @@ -197,7 +202,7 @@ public class ItemRegistry { } // Add the loadstone compass since it doesn't exist on java but we need it for item conversion - ITEM_ENTRIES.put(itemIndex, new ItemEntry("minecraft:lodestone_compass", itemIndex, + ITEM_ENTRIES.put(itemIndex, new ItemEntry("minecraft:lodestone_compass", "minecraft:lodestone_compass", itemIndex, lodestoneCompassId, 0, false)); /* Load creative items */ @@ -269,23 +274,6 @@ public class ItemRegistry { }); } - /** - * Finds the Bedrock string identifier of an ItemEntry - * - * @param entry the ItemEntry to search for - * @return the Bedrock identifier - */ - public static String getBedrockIdentifier(ItemEntry entry) { - String blockName = ""; - for (StartGamePacket.ItemEntry startGamePacketItemEntry : ItemRegistry.ITEMS) { - if (startGamePacketItemEntry.getId() == (short) entry.getBedrockId()) { - blockName = startGamePacketItemEntry.getIdentifier(); // Find the Bedrock string name - break; - } - } - return blockName; - } - /** * Gets a Bedrock {@link ItemData} from a {@link JsonNode} * @param itemNode the JSON node that contains ProxyPass-compatible Bedrock item data diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java index 00c9138a..0df179cf 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java @@ -34,16 +34,13 @@ import com.nukkitx.nbt.NbtType; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; -import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.ItemRemapper; +import org.geysermc.connector.network.translators.chat.MessageTranslator; import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.utils.FileUtils; import org.geysermc.connector.utils.LanguageUtils; -import org.geysermc.connector.network.translators.chat.MessageTranslator; import org.reflections.Reflections; import java.util.*; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ToolItemEntry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ToolItemEntry.java index 10edcdec..dde57700 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ToolItemEntry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ToolItemEntry.java @@ -32,8 +32,8 @@ public class ToolItemEntry extends ItemEntry { private final String toolType; private final String toolTier; - public ToolItemEntry(String javaIdentifier, int javaId, int bedrockId, int bedrockData, String toolType, String toolTier, boolean isBlock) { - super(javaIdentifier, javaId, bedrockId, bedrockData, isBlock); + public ToolItemEntry(String javaIdentifier, String bedrockIdentifier, int javaId, int bedrockId, int bedrockData, String toolType, String toolTier, boolean isBlock) { + super(javaIdentifier, bedrockIdentifier, javaId, bedrockId, bedrockData, isBlock); this.toolType = toolType; this.toolTier = toolTier; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/CompassTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/CompassTranslator.java index 159b9ab4..92ec67dd 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/CompassTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/CompassTranslator.java @@ -53,7 +53,7 @@ public class CompassTranslator extends ItemTranslator { Tag lodestoneTag = itemStack.getNbt().get("LodestoneTracked"); if (lodestoneTag instanceof ByteTag) { // Get the fake lodestonecompass entry - itemEntry = ItemRegistry.getItemEntry("minecraft:lodestonecompass"); + itemEntry = ItemRegistry.getItemEntry("minecraft:lodestone_compass"); // Get the loadstone pos CompoundTag loadstonePos = itemStack.getNbt().get("LodestonePos"); @@ -83,7 +83,7 @@ public class CompassTranslator extends ItemTranslator { @Override public ItemStack translateToJava(ItemData itemData, ItemEntry itemEntry) { boolean isLoadstone = false; - if (itemEntry.getJavaIdentifier().equals("minecraft:lodestonecompass")) { + if (itemEntry.getBedrockIdentifier().equals("minecraft:lodestone_compass")) { // Revert the entry back to the compass itemEntry = ItemRegistry.getItemEntry("minecraft:compass"); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/CrossbowTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/CrossbowTranslator.java index a05b9c8b..97da8669 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/CrossbowTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/CrossbowTranslator.java @@ -45,15 +45,15 @@ public class CrossbowTranslator extends NbtItemStackTranslator { if (!chargedProjectiles.getValue().isEmpty()) { CompoundTag projectile = (CompoundTag) chargedProjectiles.getValue().get(0); - ItemEntry entry = ItemRegistry.getItemEntry((String) projectile.get("id").getValue()); - if (entry == null) return; + ItemEntry projectileEntry = ItemRegistry.getItemEntry((String) projectile.get("id").getValue()); + if (projectileEntry == null) return; CompoundTag tag = projectile.get("tag"); ItemStack itemStack = new ItemStack(itemEntry.getJavaId(), (byte) projectile.get("Count").getValue(), tag); ItemData itemData = ItemTranslator.translateToBedrock(session, itemStack); CompoundTag newProjectile = new CompoundTag("chargedItem"); newProjectile.put(new ByteTag("Count", (byte) itemData.getCount())); - newProjectile.put(new StringTag("Name", ItemRegistry.getBedrockIdentifier(entry))); + newProjectile.put(new StringTag("Name", projectileEntry.getBedrockIdentifier())); newProjectile.put(new ShortTag("Damage", itemData.getDamage())); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java index 6ecb9a44..126d2e1f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java @@ -50,9 +50,8 @@ public class ShulkerBoxItemTranslator extends NbtItemStackTranslator { boxItemTag.put(new ByteTag("WasPickedUp", (byte) 0)); // ??? ItemEntry boxItemEntry = ItemRegistry.getItemEntry(((StringTag) itemData.get("id")).getValue()); - String blockName = ItemRegistry.getBedrockIdentifier(boxItemEntry); - boxItemTag.put(new StringTag("Name", blockName)); + boxItemTag.put(new StringTag("Name", boxItemEntry.getBedrockIdentifier())); boxItemTag.put(new ShortTag("Damage", (short) boxItemEntry.getBedrockData())); boxItemTag.put(new ByteTag("Count", ((ByteTag) itemData.get("Count")).getValue())); if (itemData.contains("tag")) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaTradeListTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaTradeListTranslator.java index e7e6df08..ad422a4c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaTradeListTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaTradeListTranslator.java @@ -139,7 +139,7 @@ public class JavaTradeListTranslator extends PacketTranslator Date: Wed, 18 Nov 2020 23:18:36 +0000 Subject: [PATCH 046/116] More chat fixes (#1557) * Fix positional translation arguments not being handled * Fix locale fallback * Fix command completion * Remove the expensive call to `containsKey` * Unify adventure versions * Fix some more formatting issues due to parity * Fix and update tests * Update adventure * Add Javadoc for getCommandNames * Formatting Co-authored-by: Camotoy <20743703+DoctorMacc@users.noreply.github.com> --- .../command/GeyserBungeeCommandExecutor.java | 2 +- .../command/GeyserSpigotCommandExecutor.java | 2 +- .../command/GeyserSpongeCommandExecutor.java | 2 +- .../GeyserVelocityCommandExecutor.java | 28 +++++++++++++------ connector/pom.xml | 8 +++--- .../connector/command/CommandManager.java | 11 ++++++-- .../translators/chat/MessageTranslator.java | 14 ++++++++-- .../chat/MinecraftTranslationRegistry.java | 10 +++++++ .../connector/utils/LanguageUtils.java | 6 +++- .../chat/MessageTranslatorTest.java | 20 +++++++++---- 10 files changed, 76 insertions(+), 27 deletions(-) diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/GeyserBungeeCommandExecutor.java b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/GeyserBungeeCommandExecutor.java index ff7a2e3d..c25da086 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/GeyserBungeeCommandExecutor.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/GeyserBungeeCommandExecutor.java @@ -67,7 +67,7 @@ public class GeyserBungeeCommandExecutor extends Command implements TabExecutor @Override public Iterable onTabComplete(CommandSender sender, String[] args) { if (args.length == 1) { - return Arrays.asList("?", "help", "reload", "shutdown", "stop"); + return connector.getCommandManager().getCommandNames(); } return new ArrayList<>(); } diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/GeyserSpigotCommandExecutor.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/GeyserSpigotCommandExecutor.java index 0edb8448..1ff3e7fe 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/GeyserSpigotCommandExecutor.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/GeyserSpigotCommandExecutor.java @@ -67,7 +67,7 @@ public class GeyserSpigotCommandExecutor implements TabExecutor { @Override public List onTabComplete(CommandSender sender, Command command, String label, String[] args) { if (args.length == 1) { - return Arrays.asList("?", "help", "reload", "shutdown", "stop"); + return connector.getCommandManager().getCommandNames(); } return new ArrayList<>(); } diff --git a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/GeyserSpongeCommandExecutor.java b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/GeyserSpongeCommandExecutor.java index c77e8271..3171a1a9 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/GeyserSpongeCommandExecutor.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/GeyserSpongeCommandExecutor.java @@ -70,7 +70,7 @@ public class GeyserSpongeCommandExecutor implements CommandCallable { @Override public List getSuggestions(CommandSource source, String arguments, @Nullable Location targetPosition) throws CommandException { if (arguments.split(" ").length == 1) { - return Arrays.asList("?", "help", "reload", "shutdown", "stop"); + return connector.getCommandManager().getCommandNames(); } return new ArrayList<>(); } diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandExecutor.java b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandExecutor.java index c329fb19..30c47413 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandExecutor.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandExecutor.java @@ -25,8 +25,8 @@ package org.geysermc.platform.velocity.command; -import com.velocitypowered.api.command.Command; import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.command.SimpleCommand; import lombok.AllArgsConstructor; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.CommandSender; @@ -34,29 +34,39 @@ import org.geysermc.connector.command.GeyserCommand; import org.geysermc.connector.common.ChatColor; import org.geysermc.connector.utils.LanguageUtils; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; @AllArgsConstructor -public class GeyserVelocityCommandExecutor implements Command { +public class GeyserVelocityCommandExecutor implements SimpleCommand { private final GeyserConnector connector; @Override - public void execute(CommandSource source, String[] args) { - if (args.length > 0) { - if (getCommand(args[0]) != null) { - if (!source.hasPermission(getCommand(args[0]).getPermission())) { - CommandSender sender = new VelocityCommandSender(source); + public void execute(Invocation invocation) { + if (invocation.arguments().length > 0) { + if (getCommand(invocation.arguments()[0]) != null) { + if (!invocation.source().hasPermission(getCommand(invocation.arguments()[0]).getPermission())) { + CommandSender sender = new VelocityCommandSender(invocation.source()); sender.sendMessage(ChatColor.RED + LanguageUtils.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.getLocale())); return; } - getCommand(args[0]).execute(new VelocityCommandSender(source), args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]); + getCommand(invocation.arguments()[0]).execute(new VelocityCommandSender(invocation.source()), invocation.arguments().length > 1 ? Arrays.copyOfRange(invocation.arguments(), 1, invocation.arguments().length) : new String[0]); } } else { - getCommand("help").execute(new VelocityCommandSender(source), new String[0]); + getCommand("help").execute(new VelocityCommandSender(invocation.source()), new String[0]); } } + @Override + public List suggest(Invocation invocation) { + if (invocation.arguments().length == 0) { + return connector.getCommandManager().getCommandNames(); + } + return new ArrayList<>(); + } + private GeyserCommand getCommand(String label) { return connector.getCommandManager().getCommands().get(label); } diff --git a/connector/pom.xml b/connector/pom.xml index 7c44ddfd..95794ff2 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -136,21 +136,21 @@ 2.1.3 - net.kyori + com.github.kyoripowered.adventure adventure-api - 4.1.1 + 7acd956 compile com.github.kyoripowered.adventure adventure-text-serializer-gson - 4d8a67d798 + 7acd956 compile com.github.kyoripowered.adventure adventure-text-serializer-legacy - 0599048 + 7acd956 compile diff --git a/connector/src/main/java/org/geysermc/connector/command/CommandManager.java b/connector/src/main/java/org/geysermc/connector/command/CommandManager.java index 7adce430..822b6dea 100644 --- a/connector/src/main/java/org/geysermc/connector/command/CommandManager.java +++ b/connector/src/main/java/org/geysermc/connector/command/CommandManager.java @@ -31,9 +31,7 @@ import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.defaults.*; import org.geysermc.connector.utils.LanguageUtils; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; +import java.util.*; public abstract class CommandManager { @@ -92,6 +90,13 @@ public abstract class CommandManager { cmd.execute(sender, args); } + /** + * @return a list of all subcommands under {@code /geyser}. + */ + public List getCommandNames() { + return Arrays.asList(connector.getCommandManager().getCommands().keySet().toArray(new String[0])); + } + /** * Returns the description of the given command * diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/chat/MessageTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/chat/MessageTranslator.java index be01362f..b7e6838e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/chat/MessageTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/chat/MessageTranslator.java @@ -84,7 +84,17 @@ public class MessageTranslator { Locale localeCode = Locale.forLanguageTag(locale.replace('_', '-')); component = RENDERER.render(component, localeCode); - return LegacyComponentSerializer.legacySection().serialize(component); + String legacy = LegacyComponentSerializer.legacySection().serialize(component); + + // Strip strikethrough and underline as they are not supported on bedrock + legacy = legacy.replaceAll("\u00a7[mn]", ""); + + // Make color codes reset formatting like Java + // See https://minecraft.gamepedia.com/Formatting_codes#Usage + legacy = legacy.replaceAll("\u00a7([0-9a-f])", "\u00a7r\u00a7$1"); + legacy = legacy.replaceAll("\u00a7r\u00a7r", "\u00a7r"); + + return legacy; } public static String convertMessage(String message) { @@ -106,7 +116,7 @@ public class MessageTranslator { String convertedMessage = convertMessage(convertToJavaMessage(message), locale); // We have to do this since Adventure strips the starting reset character - if (message.startsWith(getColor(ChatColor.RESET))) { + if (message.startsWith(getColor(ChatColor.RESET)) && !convertedMessage.startsWith(getColor(ChatColor.RESET))) { convertedMessage = getColor(ChatColor.RESET) + convertedMessage; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/chat/MinecraftTranslationRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/chat/MinecraftTranslationRegistry.java index a23167ac..127e0060 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/chat/MinecraftTranslationRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/chat/MinecraftTranslationRegistry.java @@ -61,6 +61,16 @@ public class MinecraftTranslationRegistry implements TranslationRegistry { } m.appendTail(sb); + // Replace the `%x$s` with numbered inserts `{x}` + p = Pattern.compile("%([0-9]+)\\$s"); + m = p.matcher(sb.toString()); + sb = new StringBuffer(); + while (m.find()) { + i = Integer.parseInt(m.group(1)) - 1; + m.appendReplacement(sb, "{" + i + "}"); + } + m.appendTail(sb); + return new MessageFormat(sb.toString(), locale); } diff --git a/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java index 06d2936e..87722e5b 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java @@ -105,7 +105,11 @@ public class LanguageUtils { locale = formatLocale(locale); Properties properties = LOCALE_MAPPINGS.get(locale); - String formatString = properties.getProperty(key); + String formatString = null; + + if (properties != null) { + formatString = properties.getProperty(key); + } // Try and get the key from the default locale if (formatString == null) { diff --git a/connector/src/test/java/org/geysermc/connector/network/translators/chat/MessageTranslatorTest.java b/connector/src/test/java/org/geysermc/connector/network/translators/chat/MessageTranslatorTest.java index 5d52c79b..4dd6a04c 100644 --- a/connector/src/test/java/org/geysermc/connector/network/translators/chat/MessageTranslatorTest.java +++ b/connector/src/test/java/org/geysermc/connector/network/translators/chat/MessageTranslatorTest.java @@ -39,22 +39,31 @@ public class MessageTranslatorTest { @Before public void setUp() throws Exception { messages.put("{\"text\":\"\",\"extra\":[{\"text\":\"DoctorMad9952 joined the game\",\"color\":\"yellow\"}]}", - "§eDoctorMad9952 joined the game"); + "§r§eDoctorMad9952 joined the game"); messages.put("{\"text\":\"\",\"extra\":[\"Plugins (3): \",{\"text\":\"WorldEdit\",\"color\":\"green\"},{\"text\":\", \",\"color\":\"white\"},{\"text\":\"ViaVersion\",\"color\":\"green\"},{\"text\":\", \",\"color\":\"white\"},{\"text\":\"Geyser-Spigot\",\"color\":\"green\"}]}", - "Plugins (3): §aWorldEdit§f, §aViaVersion§f, §aGeyser-Spigot"); + "Plugins (3): §r§aWorldEdit§r§f, §r§aViaVersion§r§f, §r§aGeyser-Spigot"); // RGB downgrade test messages.put("{\"extra\":[{\"text\":\" \"},{\"color\":\"gold\",\"text\":\"The \"},{\"color\":\"#E14248\",\"obfuscated\":true,\"text\":\"||\"},{\"color\":\"#3AA9FF\",\"bold\":true,\"text\":\"CubeCraft\"},{\"color\":\"#E14248\",\"obfuscated\":true,\"text\":\"||\"},{\"color\":\"gold\",\"text\":\" Network \"},{\"color\":\"green\",\"text\":\"[1.8/1.9+]\\n \"},{\"color\":\"#f5e342\",\"text\":\"✦ \"},{\"color\":\"#b042f5\",\"bold\":true,\"text\":\"N\"},{\"color\":\"#c142f5\",\"bold\":true,\"text\":\"E\"},{\"color\":\"#d342f5\",\"bold\":true,\"text\":\"W\"},{\"color\":\"#e442f5\",\"bold\":true,\"text\":\":\"},{\"color\":\"#f542f5\",\"bold\":true,\"text\":\" \"},{\"color\":\"#bcf542\",\"bold\":true,\"text\":\"A\"},{\"color\":\"#acee3f\",\"bold\":true,\"text\":\"M\"},{\"color\":\"#9ce73c\",\"bold\":true,\"text\":\"O\"},{\"color\":\"#8ce039\",\"bold\":true,\"text\":\"N\"},{\"color\":\"#7cd936\",\"bold\":true,\"text\":\"G\"},{\"color\":\"#6cd233\",\"bold\":true,\"text\":\" \"},{\"color\":\"#5ccb30\",\"bold\":true,\"text\":\"S\"},{\"color\":\"#4cc42d\",\"bold\":true,\"text\":\"L\"},{\"color\":\"#3cbd2a\",\"bold\":true,\"text\":\"I\"},{\"color\":\"#2cb627\",\"bold\":true,\"text\":\"M\"},{\"color\":\"#1caf24\",\"bold\":true,\"text\":\"E\"},{\"color\":\"#0ca821\",\"bold\":true,\"text\":\"S\"},{\"color\":\"#f5e342\",\"text\":\" \"},{\"color\":\"#6d7c87\",\"text\":\"(kinda sus) \"},{\"color\":\"#f5e342\",\"text\":\"✦\"}],\"text\":\"\"}", - " §6The §c§k||§r§3§lCubeCraft§r§c§k||§r§6 Network §a[1.8/1.9+]\n" + - " §e✦ §d§lN§r§d§lE§r§d§lW§r§d§l:§r§d§l §r§e§lA§r§e§lM§r§a§lO§r§a§lN§r§a§lG§r§a§l §r§a§lS§r§a§lL§r§2§lI§r§2§lM§r§2§lE§r§2§lS§r§e §8(kinda sus) §e✦"); + " §r§6The §r§c§k||§r§3§lCubeCraft§r§c§k||§r§6 Network §r§a[1.8/1.9+]\n" + + " §r§e✦ §r§d§lN§r§d§lE§r§d§lW§r§d§l:§r§d§l §r§e§lA§r§e§lM§r§a§lO§r§a§lN§r§a§lG§r§a§l §r§a§lS§r§a§lL§r§2§lI§r§2§lM§r§2§lE§r§2§lS§r§e §r§8(kinda sus) §r§e✦"); + + // Color code format resetting + messages.put("{\"text\":\"\",\"extra\":[{\"text\":\"\",\"extra\":[{\"text\":\"[\",\"color\":\"gray\"},{\"text\":\"H\",\"color\":\"yellow\"},{\"text\":\"]\",\"color\":\"gray\"},{\"text\":\" \",\"color\":\"white\"},{\"text\":\"GUEST\",\"color\":\"#b7b7b7\",\"bold\":true}]},{\"text\":\"\",\"extra\":[{\"text\":\" \",\"bold\":true},{\"text\":\"»\",\"color\":\"blue\"},{\"text\":\" \",\"color\":\"gray\"}]},{\"text\":\"\",\"extra\":[{\"text\":\"rtm516\",\"color\":\"white\"},{\"text\":\": \",\"color\":\"gray\"},{\"text\":\"\",\"color\":\"white\"}]},{\"text\":\"\",\"extra\":[{\"text\":\"This is an amazing bedrock test message\",\"color\":\"white\"}]}]}\n", + "§r§7[§r§eH§r§7]§r§f §r§7§lGUEST§r§l §r§9»§r§7 §r§frtm516§r§7: §r§fThis is an amazing bedrock test message"); + + // Test translation and positional arguments + // Disabled due to not having an GeyserConnector instance, hence it fails + //messages.put("{\"translate\":\"death.attack.player\",\"with\":[{\"text\":\"rtm516\",\"insertion\":\"rtm516\"},{\"text\":\"*invincible_rt\",\"insertion\":\"*invincible_rt\"}]}", + // "rtm516 was slain by *invincible_rt"); } @Test public void convertMessage() { for (Map.Entry entry : messages.entrySet()) { String bedrockMessage = MessageTranslator.convertMessage(entry.getKey(), "en_US"); - Assert.assertEquals("Translation of messages is incorrect", bedrockMessage, entry.getValue()); + Assert.assertEquals("Translation of messages is incorrect", entry.getValue(), bedrockMessage); } } @@ -63,5 +72,6 @@ public class MessageTranslatorTest { Assert.assertEquals("All newline message is not handled properly", "\n\n\n\n", MessageTranslator.convertMessageLenient("\n\n\n\n")); Assert.assertEquals("Empty message is not handled properly", "", MessageTranslator.convertMessageLenient("")); Assert.assertEquals("Reset before message is not handled properly", "§r§eGame Selector", MessageTranslator.convertMessageLenient("§r§eGame Selector")); + Assert.assertEquals("Unimplemented formatting chars not stripped", "Bold Underline", MessageTranslator.convertMessageLenient("§m§nBold Underline")); } } From a70d3e2150e8b50c3fd2b74710d6e8fa504a6d07 Mon Sep 17 00:00:00 2001 From: circuit10 Date: Fri, 20 Nov 2020 19:56:39 +0000 Subject: [PATCH 047/116] Fix inconsistencies with movement and position (#699) Movement is now significant better, especially on slabs, stairs, and other half-blocks. Co-authored-by: RednedEpic Co-authored-by: DoctorMacc Co-authored-by: Tim203 Co-authored-by: Camotoy <20743703+DoctorMacc@users.noreply.github.com> --- bootstrap/spigot/pom.xml | 5 + .../platform/spigot/GeyserSpigotPlugin.java | 99 ++++++++- .../world/GeyserSpigotBlockPlaceListener.java | 15 +- .../GeyserSpigot1_12NativeWorldManager.java | 59 +++++ .../manager/GeyserSpigot1_12WorldManager.java | 141 ++++++++++++ .../GeyserSpigotFallbackWorldManager.java | 62 ++++++ .../GeyserSpigotLegacyNativeWorldManager.java | 79 +++++++ .../GeyserSpigotNativeWorldManager.java | 51 +++++ .../GeyserSpigotWorldManager.java | 118 +++------- connector/pom.xml | 2 + .../geysermc/connector/GeyserConnector.java | 2 + .../configuration/GeyserConfiguration.java | 2 + .../GeyserJacksonConfiguration.java | 3 + .../org/geysermc/connector/entity/Entity.java | 3 +- .../connector/entity/FireworkEntity.java | 1 + .../entity/{ => player}/PlayerEntity.java | 27 ++- .../entity/player/SessionPlayerEntity.java | 67 ++++++ .../connector/entity/type/EntityType.java | 1 + .../network/session/GeyserSession.java | 112 ++++++++-- .../network/session/cache/EntityCache.java | 2 +- .../network/session/cache/TeleportCache.java | 42 +++- .../bedrock/BedrockRespawnTranslator.java | 2 +- ...SetLocalPlayerAsInitializedTranslator.java | 2 +- .../player/BedrockActionTranslator.java | 1 + .../player/BedrockMovePlayerTranslator.java | 163 +++++++++----- .../translators/collision/BoundingBox.java | 52 +++++ .../collision/CollisionManager.java | 208 ++++++++++++++++++ .../collision/CollisionRemapper.java | 57 +++++ .../collision/CollisionTranslator.java | 192 ++++++++++++++++ .../collision/translators/BlockCollision.java | 154 +++++++++++++ .../collision/translators/DoorCollision.java | 92 ++++++++ .../collision/translators/EmptyCollision.java | 35 +++ .../translators/GrassPathCollision.java | 49 +++++ .../collision/translators/OtherCollision.java | 53 +++++ .../translators/ScaffoldingCollision.java | 72 ++++++ .../collision/translators/SnowCollision.java | 103 +++++++++ .../collision/translators/SolidCollision.java | 39 ++++ .../translators/TrapdoorCollision.java | 105 +++++++++ .../java/JavaJoinGameTranslator.java | 2 +- .../entity/JavaEntityEffectTranslator.java | 8 +- .../JavaEntityRemoveEffectTranslator.java | 8 +- .../player/JavaPlayerAbilitiesTranslator.java | 10 +- .../player/JavaPlayerListEntryTranslator.java | 2 +- .../JavaPlayerPositionRotationTranslator.java | 16 +- .../spawn/JavaSpawnPlayerTranslator.java | 2 +- .../world/JavaNotifyClientTranslator.java | 2 +- .../world/block/BlockTranslator.java | 8 + .../geysermc/connector/utils/BlockUtils.java | 4 +- .../connector/utils/DimensionUtils.java | 4 +- .../geysermc/connector/utils/SkinUtils.java | 2 +- connector/src/main/resources/config.yml | 21 +- connector/src/main/resources/languages | 2 +- connector/src/main/resources/mappings | 2 +- 53 files changed, 2117 insertions(+), 248 deletions(-) create mode 100644 bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12NativeWorldManager.java create mode 100644 bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12WorldManager.java create mode 100644 bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotFallbackWorldManager.java create mode 100644 bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotLegacyNativeWorldManager.java create mode 100644 bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotNativeWorldManager.java rename bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/{ => manager}/GeyserSpigotWorldManager.java (59%) rename connector/src/main/java/org/geysermc/connector/entity/{ => player}/PlayerEntity.java (94%) create mode 100644 connector/src/main/java/org/geysermc/connector/entity/player/SessionPlayerEntity.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/collision/BoundingBox.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/collision/CollisionManager.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/collision/CollisionRemapper.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/collision/CollisionTranslator.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/BlockCollision.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/DoorCollision.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/EmptyCollision.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/GrassPathCollision.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/OtherCollision.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/ScaffoldingCollision.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/SnowCollision.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/SolidCollision.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/TrapdoorCollision.java diff --git a/bootstrap/spigot/pom.xml b/bootstrap/spigot/pom.xml index f3b9cf88..adaa7557 100644 --- a/bootstrap/spigot/pom.xml +++ b/bootstrap/spigot/pom.xml @@ -29,6 +29,11 @@ 3.2.0 provided + + org.geysermc.adapters + spigot-all + 1.0-SNAPSHOT + ${outputName}-Spigot diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java index 741763ec..39d4f993 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java @@ -25,8 +25,10 @@ package org.geysermc.platform.spigot; +import com.github.steveice10.mc.protocol.MinecraftConstants; import org.bukkit.Bukkit; import org.bukkit.plugin.java.JavaPlugin; +import org.geysermc.adapters.spigot.SpigotAdapters; import org.geysermc.common.PlatformType; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.bootstrap.GeyserBootstrap; @@ -42,18 +44,23 @@ import org.geysermc.platform.spigot.command.GeyserSpigotCommandExecutor; import org.geysermc.platform.spigot.command.GeyserSpigotCommandManager; import org.geysermc.platform.spigot.command.SpigotCommandSender; import org.geysermc.platform.spigot.world.GeyserSpigotBlockPlaceListener; -import org.geysermc.platform.spigot.world.GeyserSpigotWorldManager; +import org.geysermc.platform.spigot.world.manager.*; +import us.myles.ViaVersion.api.Pair; import us.myles.ViaVersion.api.Via; +import us.myles.ViaVersion.api.data.MappingData; +import us.myles.ViaVersion.api.protocol.Protocol; +import us.myles.ViaVersion.api.protocol.ProtocolRegistry; +import us.myles.ViaVersion.api.protocol.ProtocolVersion; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.List; import java.util.UUID; import java.util.logging.Level; public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { - private GeyserSpigotCommandManager geyserCommandManager; private GeyserSpigotConfiguration geyserConfig; private GeyserSpigotLogger geyserLogger; @@ -142,8 +149,48 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { // Set if we need to use a different method for getting a player's locale SpigotCommandSender.setUseLegacyLocaleMethod(!isCompatible(Bukkit.getServer().getVersion(), "1.12.0")); - this.geyserWorldManager = new GeyserSpigotWorldManager(isLegacy, use3dBiomes, isViaVersion); - GeyserSpigotBlockPlaceListener blockPlaceListener = new GeyserSpigotBlockPlaceListener(connector, isLegacy, isViaVersion); + if (connector.getConfig().isUseAdapters()) { + try { + String name = Bukkit.getServer().getClass().getPackage().getName(); + String nmsVersion = name.substring(name.lastIndexOf('.') + 1); + SpigotAdapters.registerWorldAdapter(nmsVersion); + if (isViaVersion && isViaVersionNeeded()) { + if (isLegacy) { + // Pre-1.13 + this.geyserWorldManager = new GeyserSpigot1_12NativeWorldManager(); + } else { + // Post-1.13 + this.geyserWorldManager = new GeyserSpigotLegacyNativeWorldManager(this, use3dBiomes); + } + } else { + // No ViaVersion + this.geyserWorldManager = new GeyserSpigotNativeWorldManager(use3dBiomes); + } + geyserLogger.debug("Using NMS adapter: " + this.geyserWorldManager.getClass() + ", " + nmsVersion); + } catch (Exception e) { + if (geyserConfig.isDebugMode()) { + geyserLogger.debug("Error while attempting to find NMS adapter. Most likely, this can be safely ignored. :)"); + e.printStackTrace(); + } + } + } else { + geyserLogger.debug("Not using NMS adapter as it is disabled in the config."); + } + if (this.geyserWorldManager == null) { + // No NMS adapter + if (isLegacy && isViaVersion) { + // Use ViaVersion for converting pre-1.13 block states + this.geyserWorldManager = new GeyserSpigot1_12WorldManager(); + } else if (isLegacy) { + // Not sure how this happens - without ViaVersion, we don't know any block states, so just assume everything is air + this.geyserWorldManager = new GeyserSpigotFallbackWorldManager(); + } else { + // Post-1.13 + this.geyserWorldManager = new GeyserSpigotWorldManager(use3dBiomes); + } + geyserLogger.debug("Using default world manager: " + this.geyserWorldManager.getClass()); + } + GeyserSpigotBlockPlaceListener blockPlaceListener = new GeyserSpigotBlockPlaceListener(connector, this.geyserWorldManager); Bukkit.getServer().getPluginManager().registerEvents(blockPlaceListener, this); @@ -152,8 +199,9 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { @Override public void onDisable() { - if (connector != null) + if (connector != null) { connector.shutdown(); + } } @Override @@ -186,6 +234,11 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { return getDataFolder().toPath(); } + @Override + public BootstrapDumpInfo getDumpInfo() { + return new GeyserSpigotDumpInfo(); + } + public boolean isCompatible(String version, String whichVersion) { int[] currentVersion = parseVersion(version); int[] otherVersion = parseVersion(whichVersion); @@ -213,15 +266,43 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { String t = stringArray[index].replaceAll("\\D", ""); try { temp[index] = Integer.parseInt(t); - } catch(NumberFormatException ex) { + } catch (NumberFormatException ex) { temp[index] = 0; } } return temp; } - @Override - public BootstrapDumpInfo getDumpInfo() { - return new GeyserSpigotDumpInfo(); + /** + * @return the server version before ViaVersion finishes initializing + */ + public ProtocolVersion getServerProtocolVersion() { + String bukkitVersion = Bukkit.getServer().getVersion(); + // Turn "(MC: 1.16.4)" into 1.16.4. + String version = bukkitVersion.split("\\(MC: ")[1].split("\\)")[0]; + return ProtocolVersion.getClosest(version); + } + + /** + * This function should not run unless ViaVersion is installed on the server. + * + * @return true if there is any block mappings difference between the server and client. + */ + private boolean isViaVersionNeeded() { + ProtocolVersion serverVersion = getServerProtocolVersion(); + List> protocolList = ProtocolRegistry.getProtocolPath(MinecraftConstants.PROTOCOL_VERSION, + serverVersion.getVersion()); + if (protocolList == null) { + // No translation needed! + return false; + } + for (int i = protocolList.size() - 1; i >= 0; i--) { + MappingData mappingData = protocolList.get(i).getValue().getMappingData(); + if (mappingData != null) { + return true; + } + } + // All mapping data is null, which means client and server block states are the same + return false; } } diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotBlockPlaceListener.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotBlockPlaceListener.java index cb59e202..55a368be 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotBlockPlaceListener.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotBlockPlaceListener.java @@ -36,13 +36,13 @@ import org.bukkit.event.block.BlockPlaceEvent; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.block.BlockTranslator; +import org.geysermc.platform.spigot.world.manager.GeyserSpigotWorldManager; @AllArgsConstructor public class GeyserSpigotBlockPlaceListener implements Listener { private final GeyserConnector connector; - private final boolean isLegacy; - private final boolean isViaVersion; + private final GeyserSpigotWorldManager worldManager; @EventHandler public void place(final BlockPlaceEvent event) { @@ -52,14 +52,13 @@ public class GeyserSpigotBlockPlaceListener implements Listener { placeBlockSoundPacket.setSound(SoundEvent.PLACE); placeBlockSoundPacket.setPosition(Vector3f.from(event.getBlockPlaced().getX(), event.getBlockPlaced().getY(), event.getBlockPlaced().getZ())); placeBlockSoundPacket.setBabySound(false); - String javaBlockId; - if (isLegacy) { - javaBlockId = BlockTranslator.getJavaIdBlockMap().inverse().get(GeyserSpigotWorldManager.getLegacyBlock(session, - event.getBlockPlaced().getX(), event.getBlockPlaced().getY(), event.getBlockPlaced().getZ(), isViaVersion)); + if (worldManager.isLegacy()) { + placeBlockSoundPacket.setExtraData(BlockTranslator.getBedrockBlockId(worldManager.getBlockAt(session, + event.getBlockPlaced().getX(), event.getBlockPlaced().getY(), event.getBlockPlaced().getZ()))); } else { - javaBlockId = event.getBlockPlaced().getBlockData().getAsString(); + String javaBlockId = event.getBlockPlaced().getBlockData().getAsString(); + placeBlockSoundPacket.setExtraData(BlockTranslator.getBedrockBlockId(BlockTranslator.getJavaIdBlockMap().getOrDefault(javaBlockId, BlockTranslator.JAVA_AIR_ID))); } - placeBlockSoundPacket.setExtraData(BlockTranslator.getBedrockBlockId(BlockTranslator.getJavaIdBlockMap().getOrDefault(javaBlockId, 0))); placeBlockSoundPacket.setIdentifier(":"); session.sendUpstreamPacket(placeBlockSoundPacket); session.setLastBlockPlacePosition(null); diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12NativeWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12NativeWorldManager.java new file mode 100644 index 00000000..f58b75cd --- /dev/null +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12NativeWorldManager.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2019-2020 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.platform.spigot.world.manager; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.geysermc.adapters.spigot.SpigotAdapters; +import org.geysermc.adapters.spigot.SpigotWorldAdapter; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; +import us.myles.ViaVersion.api.Via; +import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.storage.BlockStorage; + +/** + * Used with ViaVersion and pre-1.13. + */ +public class GeyserSpigot1_12NativeWorldManager extends GeyserSpigot1_12WorldManager { + private final SpigotWorldAdapter adapter; + + public GeyserSpigot1_12NativeWorldManager() { + this.adapter = SpigotAdapters.getWorldAdapter(); + // Unlike post-1.13, we can't build up a cache of block states, because block entities need some special conversion + } + + @Override + public int getBlockAt(GeyserSession session, int x, int y, int z) { + Player player = Bukkit.getPlayer(session.getPlayerEntity().getUsername()); + if (player == null) { + return BlockTranslator.JAVA_AIR_ID; + } + // Get block entity storage + BlockStorage storage = Via.getManager().getConnection(player.getUniqueId()).get(BlockStorage.class); + int blockId = adapter.getBlockAt(player.getWorld(), x, y, z); + return getLegacyBlock(storage, blockId, x, y, z); + } +} diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12WorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12WorldManager.java new file mode 100644 index 00000000..b00ddafa --- /dev/null +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12WorldManager.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2019-2020 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.platform.spigot.world.manager; + +import com.github.steveice10.mc.protocol.data.game.chunk.Chunk; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; +import us.myles.ViaVersion.api.Pair; +import us.myles.ViaVersion.api.Via; +import us.myles.ViaVersion.api.data.MappingData; +import us.myles.ViaVersion.api.minecraft.Position; +import us.myles.ViaVersion.api.protocol.Protocol; +import us.myles.ViaVersion.api.protocol.ProtocolRegistry; +import us.myles.ViaVersion.api.protocol.ProtocolVersion; +import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.Protocol1_13To1_12_2; +import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.storage.BlockStorage; + +import java.util.List; + +/** + * Should be used when ViaVersion is present, no NMS adapter is being used, and we are pre-1.13. + * + * You need ViaVersion to connect to an older server with the Geyser-Spigot plugin. + */ +public class GeyserSpigot1_12WorldManager extends GeyserSpigotWorldManager { + /** + * Specific mapping data for 1.12 to 1.13. Used to convert the 1.12 block into the 1.13 block state. + * (Block IDs did not change between server versions until 1.13 and after) + */ + 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_12WorldManager() { + super(false); + this.mappingData1_12to1_13 = ProtocolRegistry.getProtocol(Protocol1_13To1_12_2.class).getMappingData(); + this.protocolList = ProtocolRegistry.getProtocolPath(CLIENT_PROTOCOL_VERSION, + ProtocolVersion.v1_13.getVersion()); + } + + @Override + @SuppressWarnings("deprecation") + public int getBlockAt(GeyserSession session, int x, int y, int z) { + Player player = Bukkit.getPlayer(session.getPlayerEntity().getUsername()); + if (player == null) { + return BlockTranslator.JAVA_AIR_ID; + } + // Get block entity storage + BlockStorage storage = Via.getManager().getConnection(player.getUniqueId()).get(BlockStorage.class); + Block block = player.getWorld().getBlockAt(x, y, z); + // Black magic that gets the old block state ID + int blockId = (block.getType().getId() << 4) | (block.getData() & 0xF); + return getLegacyBlock(storage, blockId, x, y, z); + } + + /** + * + * @param storage ViaVersion's block entity storage (used to fix block entity state differences) + * @param blockId the pre-1.13 block id + * @param x X coordinate of block + * @param y Y coordinate of block + * @param z Z coordinate of block + * @return the block state updated to the latest Minecraft version + */ + @SuppressWarnings("deprecation") + public int getLegacyBlock(BlockStorage storage, int blockId, int x, int y, int z) { + // Convert block state from old version (1.12.2) -> 1.13 -> 1.13.1 -> 1.14 -> 1.15 -> 1.16 -> 1.16.2 + blockId = mappingData1_12to1_13.getNewBlockId(blockId); + // Translate block entity differences - some information was stored in block tags and not block states + if (storage.isWelcome(blockId)) { // No getOrDefault method + BlockStorage.ReplacementData data = storage.get(new Position(x, (short) y, z)); + if (data != null && data.getReplacement() != -1) { + blockId = data.getReplacement(); + } + } + for (int i = protocolList.size() - 1; i >= 0; i--) { + MappingData mappingData = protocolList.get(i).getValue().getMappingData(); + if (mappingData != null) { + blockId = mappingData.getNewBlockStateId(blockId); + } + } + return blockId; + } + + @SuppressWarnings("deprecation") + @Override + public void getBlocksInSection(GeyserSession session, int x, int y, int z, Chunk chunk) { + Player player = Bukkit.getPlayer(session.getPlayerEntity().getUsername()); + if (player == null) { + return; + } + World world = player.getWorld(); + // Get block entity storage + BlockStorage storage = Via.getManager().getConnection(player.getUniqueId()).get(BlockStorage.class); + for (int blockY = 0; blockY < 16; blockY++) { // Cache-friendly iteration order + for (int blockZ = 0; blockZ < 16; blockZ++) { + for (int blockX = 0; blockX < 16; blockX++) { + Block block = world.getBlockAt(x, y, z); + // Black magic that gets the old block state ID + int blockId = (block.getType().getId() << 4) | (block.getData() & 0xF); + chunk.set(blockX, blockY, blockZ, getLegacyBlock(storage, blockId, (x << 4) + blockX, (y << 4) + blockY, (z << 4) + blockZ)); + } + } + } + } + + @Override + public boolean isLegacy() { + return true; + } +} diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotFallbackWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotFallbackWorldManager.java new file mode 100644 index 00000000..49c675a1 --- /dev/null +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotFallbackWorldManager.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2019-2020 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.platform.spigot.world.manager; + +import com.github.steveice10.mc.protocol.data.game.chunk.Chunk; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; + +/** + * Should only be used when we know {@link GeyserSpigotWorldManager#getBlockAt(GeyserSession, int, int, int)} + * cannot be accurate. Typically, this is when ViaVersion is not installed but a client still manages to connect. + * If this occurs to you somehow, please let us know!! + */ +public class GeyserSpigotFallbackWorldManager extends GeyserSpigotWorldManager { + public GeyserSpigotFallbackWorldManager() { + // Since this is pre-1.13 (and thus pre-1.15), there will never be 3D biomes. + super(false); + } + + @Override + public int getBlockAt(GeyserSession session, int x, int y, int z) { + return BlockTranslator.JAVA_AIR_ID; + } + + @Override + public void getBlocksInSection(GeyserSession session, int x, int y, int z, Chunk chunk) { + // Do nothing, since we can't do anything with the chunk + } + + @Override + public boolean hasMoreBlockDataThanChunkCache() { + return false; + } + + @Override + public boolean isLegacy() { + return true; + } +} diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotLegacyNativeWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotLegacyNativeWorldManager.java new file mode 100644 index 00000000..dec9b414 --- /dev/null +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotLegacyNativeWorldManager.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2019-2020 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.platform.spigot.world.manager; + +import com.github.steveice10.mc.protocol.MinecraftConstants; +import it.unimi.dsi.fastutil.ints.Int2IntMap; +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntList; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.platform.spigot.GeyserSpigotPlugin; +import us.myles.ViaVersion.api.Pair; +import us.myles.ViaVersion.api.data.MappingData; +import us.myles.ViaVersion.api.protocol.Protocol; +import us.myles.ViaVersion.api.protocol.ProtocolRegistry; +import us.myles.ViaVersion.api.protocol.ProtocolVersion; + +import java.util.List; + +/** + * Used when block IDs need to be translated to the latest version + */ +public class GeyserSpigotLegacyNativeWorldManager extends GeyserSpigotNativeWorldManager { + + private final Int2IntMap oldToNewBlockId; + + public GeyserSpigotLegacyNativeWorldManager(GeyserSpigotPlugin plugin, boolean use3dBiomes) { + super(use3dBiomes); + IntList allBlockStates = adapter.getAllBlockStates(); + oldToNewBlockId = new Int2IntOpenHashMap(allBlockStates.size()); + ProtocolVersion serverVersion = plugin.getServerProtocolVersion(); + List> protocolList = ProtocolRegistry.getProtocolPath(MinecraftConstants.PROTOCOL_VERSION, + serverVersion.getVersion()); + for (int oldBlockId : allBlockStates) { + int newBlockId = oldBlockId; + // protocolList should *not* be null; we checked for that before initializing this class + for (int i = protocolList.size() - 1; i >= 0; i--) { + MappingData mappingData = protocolList.get(i).getValue().getMappingData(); + if (mappingData != null) { + newBlockId = mappingData.getNewBlockStateId(newBlockId); + } + } + oldToNewBlockId.put(oldBlockId, newBlockId); + } + } + + @Override + public int getBlockAt(GeyserSession session, int x, int y, int z) { + int nativeBlockId = super.getBlockAt(session, x, y, z); + return oldToNewBlockId.getOrDefault(nativeBlockId, nativeBlockId); + } + + @Override + public boolean isLegacy() { + return true; + } +} diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotNativeWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotNativeWorldManager.java new file mode 100644 index 00000000..f703ecdb --- /dev/null +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotNativeWorldManager.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2019-2020 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.platform.spigot.world.manager; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.geysermc.adapters.spigot.SpigotAdapters; +import org.geysermc.adapters.spigot.SpigotWorldAdapter; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; + +public class GeyserSpigotNativeWorldManager extends GeyserSpigotWorldManager { + protected final SpigotWorldAdapter adapter; + + public GeyserSpigotNativeWorldManager(boolean use3dBiomes) { + super(use3dBiomes); + adapter = SpigotAdapters.getWorldAdapter(); + } + + @Override + public int getBlockAt(GeyserSession session, int x, int y, int z) { + Player player = Bukkit.getPlayer(session.getPlayerEntity().getUsername()); + if (player == null) { + return BlockTranslator.JAVA_AIR_ID; + } + return adapter.getBlockAt(player.getWorld(), x, y, z); + } +} diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotWorldManager.java similarity index 59% rename from bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java rename to bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotWorldManager.java index 3493fc25..cd1774ba 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotWorldManager.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.spigot.world; +package org.geysermc.platform.spigot.world.manager; import com.fasterxml.jackson.databind.JsonNode; import com.github.steveice10.mc.protocol.MinecraftConstants; @@ -34,6 +34,7 @@ import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.block.Biome; import org.bukkit.block.Block; +import org.bukkit.block.data.BlockData; import org.bukkit.entity.Player; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; @@ -42,38 +43,22 @@ import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.utils.FileUtils; import org.geysermc.connector.utils.GameRule; import org.geysermc.connector.utils.LanguageUtils; -import us.myles.ViaVersion.api.Pair; -import us.myles.ViaVersion.api.Via; -import us.myles.ViaVersion.api.data.MappingData; -import us.myles.ViaVersion.api.minecraft.Position; -import us.myles.ViaVersion.api.protocol.Protocol; -import us.myles.ViaVersion.api.protocol.ProtocolRegistry; -import us.myles.ViaVersion.api.protocol.ProtocolVersion; -import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.Protocol1_13To1_12_2; -import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.storage.BlockStorage; import java.io.InputStream; -import java.util.List; +/** + * The base world manager to use when there is no supported NMS revision + */ public class GeyserSpigotWorldManager extends GeyserWorldManager { /** * The current client protocol version for ViaVersion usage. */ - private static final int CLIENT_PROTOCOL_VERSION = MinecraftConstants.PROTOCOL_VERSION; + protected static final int CLIENT_PROTOCOL_VERSION = MinecraftConstants.PROTOCOL_VERSION; - /** - * Whether the server is pre-1.13. - */ - private final boolean isLegacy; /** * Whether the server is pre-1.16 and therefore does not support 3D biomes on an API level guaranteed. */ private final boolean use3dBiomes; - /** - * You need ViaVersion to connect to an older server with Geyser. - * However, we still check for ViaVersion in case there's some other way that gets Geyser on a pre-1.13 Bukkit server - */ - private final boolean isViaVersion; /** * Stores a list of {@link Biome} ordinal numbers to Minecraft biome numeric IDs. * @@ -87,10 +72,8 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { */ private final Int2IntMap biomeToIdMap = new Int2IntOpenHashMap(Biome.values().length); - public GeyserSpigotWorldManager(boolean isLegacy, boolean use3dBiomes, boolean isViaVersion) { - this.isLegacy = isLegacy; + public GeyserSpigotWorldManager(boolean use3dBiomes) { this.use3dBiomes = use3dBiomes; - this.isViaVersion = isViaVersion; // Load the values into the biome-to-ID map InputStream biomeStream = FileUtils.getResource("biomes.json"); @@ -116,83 +99,26 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { @Override public int getBlockAt(GeyserSession session, int x, int y, int z) { Player bukkitPlayer; - if ((this.isLegacy && !this.isViaVersion) - || session.getPlayerEntity() == null - || (bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) { + if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) { return BlockTranslator.JAVA_AIR_ID; } World world = bukkitPlayer.getWorld(); - if (isLegacy) { - return getLegacyBlock(session, x, y, z, true); - } - //TODO possibly: detect server version for all versions and use ViaVersion for block state mappings like below - return BlockTranslator.getJavaIdBlockMap().getOrDefault(world.getBlockAt(x, y, z).getBlockData().getAsString(), 0); - } - - public static int getLegacyBlock(GeyserSession session, int x, int y, int z, boolean isViaVersion) { - if (isViaVersion) { - Player bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername()); - // Get block entity storage - BlockStorage storage = Via.getManager().getConnection(bukkitPlayer.getUniqueId()).get(BlockStorage.class); - return getLegacyBlock(storage, bukkitPlayer.getWorld(), x, y, z); - } else { - return BlockTranslator.JAVA_AIR_ID; - } - } - - @SuppressWarnings("deprecation") - public static int getLegacyBlock(BlockStorage storage, World world, int x, int y, int z) { - Block block = world.getBlockAt(x, y, z); - // Black magic that gets the old block state ID - int blockId = (block.getType().getId() << 4) | (block.getData() & 0xF); - // Convert block state from old version (1.12.2) -> 1.13 -> 1.13.1 -> 1.14 -> 1.15 -> 1.16 -> 1.16.2 - blockId = ProtocolRegistry.getProtocol(Protocol1_13To1_12_2.class).getMappingData().getNewBlockId(blockId); - List> protocolList = ProtocolRegistry.getProtocolPath(CLIENT_PROTOCOL_VERSION, - ProtocolVersion.v1_13.getId()); - // Translate block entity differences - some information was stored in block tags and not block states - if (storage.isWelcome(blockId)) { // No getOrDefault method - BlockStorage.ReplacementData data = storage.get(new Position(x, (short) y, z)); - if (data != null && data.getReplacement() != -1) { - blockId = data.getReplacement(); - } - } - for (int i = protocolList.size() - 1; i >= 0; i--) { - MappingData mappingData = protocolList.get(i).getValue().getMappingData(); - if (mappingData != null) { - blockId = mappingData.getNewBlockStateId(blockId); - } - } - return blockId; + return BlockTranslator.getJavaIdBlockMap().getOrDefault(world.getBlockAt(x, y, z).getBlockData().getAsString(), BlockTranslator.JAVA_AIR_ID); } @Override public void getBlocksInSection(GeyserSession session, int x, int y, int z, Chunk chunk) { Player bukkitPlayer; - if ((this.isLegacy && !this.isViaVersion) - || session.getPlayerEntity() == null - || (bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) { + if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) { return; } World world = bukkitPlayer.getWorld(); - if (this.isLegacy) { - // Get block entity storage - BlockStorage storage = Via.getManager().getConnection(bukkitPlayer.getUniqueId()).get(BlockStorage.class); - for (int blockY = 0; blockY < 16; blockY++) { // Cache-friendly iteration order - for (int blockZ = 0; blockZ < 16; blockZ++) { - for (int blockX = 0; blockX < 16; blockX++) { - chunk.set(blockX, blockY, blockZ, getLegacyBlock(storage, world, (x << 4) + blockX, (y << 4) + blockY, (z << 4) + blockZ)); - } - } - } - } else { - //TODO: see above TODO in getBlockAt - for (int blockY = 0; blockY < 16; blockY++) { // Cache-friendly iteration order - for (int blockZ = 0; blockZ < 16; blockZ++) { - for (int blockX = 0; blockX < 16; blockX++) { - Block block = world.getBlockAt((x << 4) + blockX, (y << 4) + blockY, (z << 4) + blockZ); - int id = BlockTranslator.getJavaIdBlockMap().getOrDefault(block.getBlockData().getAsString(), BlockTranslator.JAVA_AIR_ID); - chunk.set(blockX, blockY, blockZ, id); - } + for (int blockY = 0; blockY < 16; blockY++) { // Cache-friendly iteration order + for (int blockZ = 0; blockZ < 16; blockZ++) { + for (int blockX = 0; blockX < 16; blockX++) { + Block block = world.getBlockAt((x << 4) + blockX, (y << 4) + blockY, (z << 4) + blockZ); + int id = BlockTranslator.getJavaIdBlockMap().getOrDefault(block.getBlockData().getAsString(), BlockTranslator.JAVA_AIR_ID); + chunk.set(blockX, blockY, blockZ, id); } } } @@ -254,4 +180,16 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { public boolean hasPermission(GeyserSession session, String permission) { return Bukkit.getPlayer(session.getPlayerEntity().getUsername()).hasPermission(permission); } + + /** + * This must be set to true if we are pre-1.13, and {@link BlockData#getAsString() does not exist}. + * + * This should be set to true if we are post-1.13 but before the latest version, and we should convert the old block state id + * to the current one. + * + * @return whether there is a difference between client block state and server block state that requires extra processing + */ + public boolean isLegacy() { + return false; + } } diff --git a/connector/pom.xml b/connector/pom.xml index 95794ff2..d7017b0d 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -257,6 +257,8 @@