From c1c2e3baa48f16aea4ffdd104b71bcb7b67ea7df Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Wed, 24 Jun 2020 17:38:41 -0400 Subject: [PATCH 01/72] Allow for newest ViaVersion to work with Geyser (#805) * Allow for newest ViaVersion to work with Geyser Most users should now just not use 2.2.3. * Update ViaVersion dependency --- bootstrap/bukkit/pom.xml | 2 +- .../java/org/geysermc/platform/bukkit/GeyserBukkitPlugin.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bootstrap/bukkit/pom.xml b/bootstrap/bukkit/pom.xml index 95926854dec..ca8188126ef 100644 --- a/bootstrap/bukkit/pom.xml +++ b/bootstrap/bukkit/pom.xml @@ -26,7 +26,7 @@ us.myles viaversion - 3.0.0-SNAPSHOT + 3.0.1 provided diff --git a/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitPlugin.java b/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitPlugin.java index 3a13777b47c..1507f9ee45e 100644 --- a/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitPlugin.java +++ b/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitPlugin.java @@ -110,8 +110,8 @@ public void onEnable() { if (Bukkit.getPluginManager().getPlugin("ViaVersion") != null) { // TODO: Update when ViaVersion updates // API changes between 2.2.3 and 3.0.0-SNAPSHOT require this check - if (!Via.getAPI().getVersion().equals("3.0.0-SNAPSHOT") && isLegacy) { - geyserLogger.info("ViaVersion detected but not ViaVersion-ABSTRACTION. Please update your ViaVersion plugin for compatibility with Geyser."); + if (Via.getAPI().getVersion().contains("2.2.3") && isLegacy) { + geyserLogger.info("ViaVersion detected but not the newest version of ViaVersion. Please update your ViaVersion plugin for compatibility with Geyser."); } else { isViaVersion = true; } From e81c6f21bf264a4a38e7c8448121009c3cfdf4a1 Mon Sep 17 00:00:00 2001 From: D3ATHBRINGER13 <53559772+D3ATHBRINGER13@users.noreply.github.com> Date: Thu, 25 Jun 2020 00:32:07 +0100 Subject: [PATCH 02/72] Bump action versions (#810) --- .github/workflows/pullrequest.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index 7f941da6b32..4a570df56bf 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -8,8 +8,8 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 - - uses: actions/cache@v1 + - uses: actions/checkout@v2 + - uses: actions/cache@v2 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} @@ -24,31 +24,31 @@ jobs: - name: Build with Maven run: mvn -B package - name: Archive artifacts (Geyser Standalone) - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v2 if: success() with: name: Geyser Standalone path: bootstrap/standalone/target/Geyser.jar - name: Archive artifacts (Geyser Bukkit) - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v2 if: success() with: name: Geyser Bukkit path: bootstrap/bukkit/target/Geyser-Bukkit.jar - name: Archive artifacts (Geyser BungeeCord) - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v2 if: success() with: name: Geyser BungeeCord path: bootstrap/bungeecord/target/Geyser-BungeeCord.jar - name: Archive artifacts (Geyser Sponge) - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v2 if: success() with: name: Geyser Sponge path: bootstrap/sponge/target/Geyser-Sponge.jar - name: Archive artifacts (Geyser Velocity) - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v2 if: success() with: name: Geyser Velocity From 61bfd46a15828dc69f816e366c86a3716c305619 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Sat, 27 Jun 2020 06:58:52 +0100 Subject: [PATCH 03/72] Fix en_us locale downloading (#809) Fixes occasional inventories not working because of being unable to read the locale. --- .../geysermc/connector/utils/LocaleUtils.java | 43 ++++++++++--------- 1 file changed, 22 insertions(+), 21 deletions(-) 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 e213fe6cc95..78752591359 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java @@ -28,6 +28,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.JsonNode; +import com.github.steveice10.mc.protocol.MinecraftConstants; import lombok.Getter; import org.geysermc.connector.GeyserConnector; @@ -35,7 +36,10 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.*; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; import java.util.zip.ZipFile; public class LocaleUtils { @@ -70,7 +74,7 @@ private static void generateAssetCache() { // Get the url for the latest version of the games manifest String latestInfoURL = ""; for (Version version : versionManifest.getVersions()) { - if (version.getId().equals(versionManifest.getLatestVersion().getRelease())) { + if (version.getId().equals(MinecraftConstants.GAME_VERSION)) { latestInfoURL = version.getUrl(); break; } @@ -84,14 +88,11 @@ private static void generateAssetCache() { // Get the individual version manifest VersionInfo versionInfo = GeyserConnector.JSON_MAPPER.readValue(WebUtils.getBody(latestInfoURL), VersionInfo.class); - // Get the smallest jar for use when downloading the en_us locale, will be either the server or client - int currentSize = Integer.MAX_VALUE; - for (VersionDownload download : versionInfo.getDownloads().values()) { - if (download.getUrl().endsWith(".jar") && download.getSize() < currentSize) { - smallestURL = download.getUrl(); - currentSize = download.getSize(); - } - } + // 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(); // Get the assets list JsonNode assets = GeyserConnector.JSON_MAPPER.readTree(WebUtils.getBody(versionInfo.getAssetIndex().getUrl())).get("objects"); @@ -160,7 +161,7 @@ private static void downloadLocale(String locale) { * @param locale Locale to load */ private static void loadLocale(String locale) { - File localeFile = new File("locales/" + locale + ".json"); + File localeFile = GeyserConnector.getInstance().getBootstrap().getConfigFolder().resolve("locales/" + locale + ".json").toFile(); // Load the locale if (localeFile.exists()) { @@ -212,21 +213,21 @@ private static void downloadEN_US(File localeFile) { // Load in the JAR as a zip and extract the file ZipFile localeJar = new ZipFile(tmpFilePath.toString()); - InputStream inputStream = localeJar.getInputStream(localeJar.getEntry("assets/minecraft/lang/en_us.json")); - FileOutputStream outputStream = new FileOutputStream(localeFile); + InputStream fileStream = localeJar.getInputStream(localeJar.getEntry("assets/minecraft/lang/en_us.json")); + FileOutputStream outStream = new FileOutputStream(localeFile); // Write the file to the locale dir - int data = inputStream.read(); - while(data != -1){ - outputStream.write(data); - data = inputStream.read(); + byte[] buf = new byte[fileStream.available()]; + int length; + while ((length = fileStream.read(buf)) != -1) { + outStream.write(buf, 0, length); } // Flush all changes to disk and cleanup - outputStream.flush(); - outputStream.close(); + outStream.flush(); + outStream.close(); - inputStream.close(); + fileStream.close(); localeJar.close(); // Delete the nolonger needed client/server jar @@ -357,4 +358,4 @@ class Asset { @JsonProperty("size") private int size; -} +} \ No newline at end of file From f2f59e4e3775dff8a24e01dbedca59634027e188 Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Sun, 28 Jun 2020 23:44:38 -0400 Subject: [PATCH 04/72] Fill in renamed villager trading values (villager trading UI now opens) --- .../translators/java/world/JavaTradeListTranslator.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 7c80d104b58..d9d23f32351 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 @@ -70,9 +70,9 @@ public void translate(ServerTradeListPacket packet, GeyserSession session) { displayName = packet.isRegularVillager() ? "Villager" : "Wandering Trader"; } updateTradePacket.setDisplayName(displayName); - //updateTradePacket.setUnknownInt(0); //TODO -// updateTradePacket.setScreen2(true); -// updateTradePacket.setWilling(true); + updateTradePacket.setSize(0); + updateTradePacket.setNewTradingUi(true); + updateTradePacket.setUsingEconomyTrade(true); updateTradePacket.setPlayerUniqueEntityId(session.getPlayerEntity().getGeyserId()); updateTradePacket.setTraderUniqueEntityId(session.getPlayerEntity().getGeyserId()); CompoundTagBuilder builder = CompoundTagBuilder.builder(); From 70009c4bf96e20274454b207416b57eacf1bafc8 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Mon, 29 Jun 2020 13:50:16 +0100 Subject: [PATCH 05/72] Clean chat code and fix skins --- connector/pom.xml | 2 +- .../connector/utils/MessageUtils.java | 19 +++++++------------ .../geysermc/connector/utils/SkinUtils.java | 1 + 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/connector/pom.xml b/connector/pom.xml index ddc62bf2a17..09171248ae4 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -105,7 +105,7 @@ com.github.GeyserMC MCProtocolLib - feature~1.16-1.12.1-1-g74ee57a-310 + feature~1.16-1.12.1-1-ge4798a3-318 compile 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 96775f24578..3a35782d4da 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java @@ -32,6 +32,7 @@ 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.text.Component; @@ -216,25 +217,19 @@ private static Message fixMessageStyle(Message message, Message parent) { if (parent == null) { return message; } - Message newMessage = message; + MessageStyle.Builder styleBuilder = message.getStyle().toBuilder(); // Copy color from parent - if (newMessage.getStyle().getColor() == ChatColor.NONE) { - JsonObject messageObject = MessageSerializer.toJsonObject(newMessage); - messageObject.addProperty("color", parent.getStyle().getColor()); - newMessage = MessageSerializer.fromJson(messageObject); + if (message.getStyle().getColor() == ChatColor.NONE) { + styleBuilder.color(parent.getStyle().getColor()); } // Copy formatting from parent - if (newMessage.getStyle().getFormats().size() == 0) { - JsonObject messageObject = MessageSerializer.toJsonObject(newMessage); - for(ChatFormat format : parent.getStyle().getFormats()) { - messageObject.addProperty(format.toString(), true); - } - newMessage = MessageSerializer.fromJson(messageObject); + if (message.getStyle().getFormats().size() == 0) { + styleBuilder.formats(parent.getStyle().getFormats()); } - return newMessage; + return message.toBuilder().style(styleBuilder.build()).build(); } public static String getBedrockMessage(Message message) { 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 ae17ed20558..9e071247769 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java @@ -96,6 +96,7 @@ public static PlayerListPacket.Entry buildEntryManually(UUID uuid, String userna entry.setXuid(""); entry.setPlatformChatId(""); entry.setTeacher(false); + entry.setTrustedSkin(true); return entry; } From 91c33242c6f91122a915f67ac6f5ee5a06052b11 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Mon, 29 Jun 2020 14:40:06 +0100 Subject: [PATCH 06/72] Fix baby states and collisions of 1.16 mobs --- .../entity/living/animal/StriderEntity.java | 1 - .../entity/living/monster/HoglinEntity.java | 52 +++++++++++++++++++ .../entity/living/monster/ZoglinEntity.java | 36 +++++++++++++ .../connector/entity/type/EntityType.java | 10 ++-- 4 files changed, 93 insertions(+), 6 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/entity/living/monster/HoglinEntity.java create mode 100644 connector/src/main/java/org/geysermc/connector/entity/living/monster/ZoglinEntity.java diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/StriderEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/StriderEntity.java index 18bb8166e6f..abf6758a2e5 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/StriderEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/StriderEntity.java @@ -40,7 +40,6 @@ public StriderEntity(long entityId, long geyserId, EntityType entityType, Vector @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 18) { metadata.getFlags().setFlag(EntityFlag.SADDLED, (boolean) entityMetadata.getValue()); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/HoglinEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/HoglinEntity.java new file mode 100644 index 00000000000..10faebc7835 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/HoglinEntity.java @@ -0,0 +1,52 @@ +/* + * 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 com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class HoglinEntity extends MonsterEntity { + + 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() == 15) { + boolean isBaby = (boolean) entityMetadata.getValue(); + if (isBaby) { + metadata.put(EntityData.SCALE, .55f); + metadata.getFlags().setFlag(EntityFlag.BABY, true); + } + } + super.updateBedrockMetadata(entityMetadata, session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/ZoglinEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/ZoglinEntity.java new file mode 100644 index 00000000000..b60a39521ed --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/ZoglinEntity.java @@ -0,0 +1,36 @@ +/* + * 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 org.geysermc.connector.entity.type.EntityType; + +public class ZoglinEntity extends HoglinEntity { + + public ZoglinEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } +} 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 4ec3471c712..70c76038de6 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 @@ -68,7 +68,7 @@ public enum EntityType { CREEPER(CreeperEntity.class, 33, 1.7f, 0.6f, 0.6f, 1.62f), SKELETON(AbstractSkeletonEntity.class, 34, 1.8f, 0.6f, 0.6f, 1.62f), SPIDER(SpiderEntity.class, 35, 0.9f, 1.4f, 1.4f, 1f), - ZOMBIFIED_PIGLIN(MonsterEntity.class, 0, 1.8f, 0.6f, 0.6f, 1.62f, "minecraft:zombie_pigman"), + ZOMBIFIED_PIGLIN(ZombieEntity.class, 36, 1.95f, 0.6f, 0.6f, 1.62f, "minecraft:zombie_pigman"), SLIME(SlimeEntity.class, 37, 0.51f), ENDERMAN(EndermanEntity.class, 38, 2.9f, 0.6f), SILVERFISH(MonsterEntity.class, 39, 0.3f, 0.4f), @@ -151,10 +151,10 @@ public enum EntityType { PANDA(PandaEntity.class, 113, 1.25f, 1.125f, 1.825f), FOX(FoxEntity.class, 121, 0.5f, 1.25f), BEE(BeeEntity.class, 122, 0.6f, 0.6f), - STRIDER(StriderEntity.class, 0, 1.7f, 0.9f, 0f, 0f, "minecraft:strider"), //TODO - update entity metadata - HOGLIN(AnimalEntity.class, 0, 0.9f, 0.9f, 0f, 0f, "minecraft:hoglin"), //TODO - ZOGLIN(MonsterEntity.class, 0, 0.9f, 0.9f, 0f, 0f, "minecraft:zoglin"), //TODO - PIGLIN(MonsterEntity.class, 0, 1.9f, 0.6f, 0f, 0f, "minecraft:piglin"), //TODO + STRIDER(StriderEntity.class, 125, 1.7f, 0.9f, 0f, 0f, "minecraft:strider"), //TODO - update entity metadata + HOGLIN(HoglinEntity.class, 124, 1.4f, 1.3965f, 1.3965f, 0f, "minecraft:hoglin"), //TODO + ZOGLIN(ZoglinEntity.class, 126, 1.4f, 1.3965f, 1.3965f, 0f, "minecraft:zoglin"), //TODO + PIGLIN(ZombieEntity.class, 123, 1.95f, 0.6f, 0.6f, 0f, "minecraft:piglin"), //TODO /** * Item frames are handled differently since they are a block in Bedrock. From d394cc6280ca0a3c7e4e9cc9feee6d968a765554 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Mon, 29 Jun 2020 15:37:54 +0100 Subject: [PATCH 07/72] Update entity metadata --- .../entity/living/animal/StriderEntity.java | 3 +++ .../{HoglinEntity.java => PiglinEntity.java} | 11 +++++++++-- .../entity/living/monster/ZoglinEntity.java | 18 +++++++++++++++++- .../connector/entity/type/EntityType.java | 4 ++-- 4 files changed, 31 insertions(+), 5 deletions(-) rename connector/src/main/java/org/geysermc/connector/entity/living/monster/{HoglinEntity.java => PiglinEntity.java} (84%) diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/StriderEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/StriderEntity.java index abf6758a2e5..e3ef9b501bb 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/StriderEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/StriderEntity.java @@ -40,6 +40,9 @@ public StriderEntity(long entityId, long geyserId, EntityType entityType, Vector @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 17) { + metadata.getFlags().setFlag(EntityFlag.ALWAYS_SHOW_NAME, (boolean) entityMetadata.getValue()); + } if (entityMetadata.getId() == 18) { metadata.getFlags().setFlag(EntityFlag.SADDLED, (boolean) entityMetadata.getValue()); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/HoglinEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/PiglinEntity.java similarity index 84% rename from connector/src/main/java/org/geysermc/connector/entity/living/monster/HoglinEntity.java rename to connector/src/main/java/org/geysermc/connector/entity/living/monster/PiglinEntity.java index 10faebc7835..78a420b8c40 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/HoglinEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/PiglinEntity.java @@ -32,9 +32,9 @@ import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; -public class HoglinEntity extends MonsterEntity { +public class PiglinEntity extends MonsterEntity { - public HoglinEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + public PiglinEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); } @@ -47,6 +47,13 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s metadata.getFlags().setFlag(EntityFlag.BABY, true); } } + if (entityMetadata.getId() == 17) { + metadata.getFlags().setFlag(EntityFlag.CHARGING, (boolean) entityMetadata.getValue()); + } + if (entityMetadata.getId() == 18) { + metadata.getFlags().setFlag(EntityFlag.DANCING, (boolean) entityMetadata.getValue()); + } + super.updateBedrockMetadata(entityMetadata, session); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/ZoglinEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/ZoglinEntity.java index b60a39521ed..4ea842110b6 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/ZoglinEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/ZoglinEntity.java @@ -25,12 +25,28 @@ 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 com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; -public class ZoglinEntity extends HoglinEntity { +public class ZoglinEntity extends MonsterEntity { public ZoglinEntity(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) { + boolean isBaby = (boolean) entityMetadata.getValue(); + if (isBaby) { + metadata.put(EntityData.SCALE, .55f); + metadata.getFlags().setFlag(EntityFlag.BABY, true); + } + } + 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 70c76038de6..1acf579f1b2 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 @@ -152,9 +152,9 @@ 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"), //TODO - update entity metadata - HOGLIN(HoglinEntity.class, 124, 1.4f, 1.3965f, 1.3965f, 0f, "minecraft:hoglin"), //TODO + HOGLIN(AnimalEntity.class, 124, 1.4f, 1.3965f, 1.3965f, 0f, "minecraft:hoglin"), //TODO ZOGLIN(ZoglinEntity.class, 126, 1.4f, 1.3965f, 1.3965f, 0f, "minecraft:zoglin"), //TODO - PIGLIN(ZombieEntity.class, 123, 1.95f, 0.6f, 0.6f, 0f, "minecraft:piglin"), //TODO + PIGLIN(PiglinEntity.class, 123, 1.95f, 0.6f, 0.6f, 0f, "minecraft:piglin"), //TODO /** * Item frames are handled differently since they are a block in Bedrock. From e77f2b5dbbbcbedcff3c96e416acaa07c000a5ca Mon Sep 17 00:00:00 2001 From: AJ Ferguson Date: Mon, 29 Jun 2020 10:59:51 -0800 Subject: [PATCH 08/72] Drop long array nbt tag when translating to bedrock --- .../connector/network/translators/item/ItemTranslator.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 6f11e94ac5e..aa75e1499fe 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 @@ -253,8 +253,10 @@ private Tag translateToBedrockNBT(com.github.steveice10.opennbt.tag.builtin.T } if (tag instanceof LongArrayTag) { - LongArrayTag longArrayTag = (LongArrayTag) tag; - return new com.nukkitx.nbt.tag.LongArrayTag(longArrayTag.getName(), longArrayTag.getValue()); + //Long array tag does not exist in BE + //LongArrayTag longArrayTag = (LongArrayTag) tag; + //return new com.nukkitx.nbt.tag.LongArrayTag(longArrayTag.getName(), longArrayTag.getValue()); + return null; } if (tag instanceof LongTag) { From ebc1f13e9b775bc103e36279d6d0e777c4f8e7ca Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Mon, 29 Jun 2020 16:03:54 -0400 Subject: [PATCH 09/72] Update dependencies ('item marked as non-null' error is fixed') --- connector/pom.xml | 2 +- connector/src/main/resources/mappings | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/connector/pom.xml b/connector/pom.xml index 09171248ae4..b4816361f32 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -105,7 +105,7 @@ com.github.GeyserMC MCProtocolLib - feature~1.16-1.12.1-1-ge4798a3-318 + feature~1.16-1.12.1-1-g10bb8e2-319 compile diff --git a/connector/src/main/resources/mappings b/connector/src/main/resources/mappings index 204c8f8dfdb..fdc65607994 160000 --- a/connector/src/main/resources/mappings +++ b/connector/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit 204c8f8dfdb1e39b881986c4968aa2575fbb5400 +Subproject commit fdc65607994b924261f43170f4d4d563b14167d7 From fc4a87a9c902e7c63d4539930060788f1fbfcc74 Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Mon, 29 Jun 2020 16:46:29 -0400 Subject: [PATCH 10/72] Fix blocks not updating --- .../src/main/java/org/geysermc/connector/utils/ChunkUtils.java | 1 + 1 file changed, 1 insertion(+) 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 9cab8605de5..5c426cb9b28 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java @@ -211,6 +211,7 @@ public static void updateBlock(GeyserSession session, int blockState, Vector3i p updateBlockPacket.setBlockPosition(position); updateBlockPacket.setRuntimeId(blockId); updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NEIGHBORS); + updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NETWORK); session.sendUpstreamPacket(updateBlockPacket); UpdateBlockPacket waterPacket = new UpdateBlockPacket(); From 4c89a8e30319631e2f3d0024b2b7739adc7886d2 Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Mon, 29 Jun 2020 17:52:59 -0400 Subject: [PATCH 11/72] Return to using Protocol develop branch --- connector/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/connector/pom.xml b/connector/pom.xml index b4816361f32..70ad0e26db6 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -31,9 +31,9 @@ compile - com.github.bundabrg.Protocol + com.nukkitx.protocol bedrock-v407 - feature~1.16-protocol-SNAPSHOT + 2.6.0-SNAPSHOT compile From 7710261b70d864429edcb887254f20be60ffc4ff Mon Sep 17 00:00:00 2001 From: rtm516 Date: Tue, 30 Jun 2020 00:52:32 +0100 Subject: [PATCH 12/72] Add Loadstone Compass tracking --- ...tionTrackingDBClientRequestTranslator.java | 82 ++++++++++++ .../translators/item/ItemRegistry.java | 3 + .../item/translators/CompassTranslator.java | 121 ++++++++++++++++++ .../connector/utils/LoadstoneTracker.java | 84 ++++++++++++ 4 files changed, 290 insertions(+) create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockPositionTrackingDBClientRequestTranslator.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/item/translators/CompassTranslator.java create mode 100644 connector/src/main/java/org/geysermc/connector/utils/LoadstoneTracker.java diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockPositionTrackingDBClientRequestTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockPositionTrackingDBClientRequestTranslator.java new file mode 100644 index 00000000000..1ca467206e6 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockPositionTrackingDBClientRequestTranslator.java @@ -0,0 +1,82 @@ +/* + * 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.nbt.CompoundTagBuilder; +import com.nukkitx.nbt.tag.IntTag; +import com.nukkitx.protocol.bedrock.packet.PositionTrackingDBClientRequestPacket; +import com.nukkitx.protocol.bedrock.packet.PositionTrackingDBServerBroadcastPacket; +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.DimensionUtils; +import org.geysermc.connector.utils.LoadstoneTracker; + +import java.util.ArrayList; +import java.util.List; + +@Translator(packet = PositionTrackingDBClientRequestPacket.class) +public class BedrockPositionTrackingDBClientRequestTranslator extends PacketTranslator { + + @Override + public void translate(PositionTrackingDBClientRequestPacket packet, GeyserSession session) { + PositionTrackingDBServerBroadcastPacket broadcastPacket = new PositionTrackingDBServerBroadcastPacket(); + broadcastPacket.setTrackingId(packet.getTrackingId()); + + // Fetch the stored Loadstone + LoadstoneTracker.LoadstonePos pos = LoadstoneTracker.getPos(packet.getTrackingId()); + + // If we don't have data for that ID tell the client its not found + if (pos == null) { + broadcastPacket.setAction(PositionTrackingDBServerBroadcastPacket.Action.NOT_FOUND); + session.sendUpstreamPacket(broadcastPacket); + return; + } + + broadcastPacket.setAction(PositionTrackingDBServerBroadcastPacket.Action.UPDATE); + + // Build the nbt data for the update + CompoundTagBuilder builder = CompoundTagBuilder.builder(); + builder.intTag("dim", DimensionUtils.javaToBedrock(pos.getDimension())); + builder.stringTag("id", String.format("%08X", packet.getTrackingId())); + + builder.byteTag("version", (byte) 1); // Not sure what this is for + builder.byteTag("status", (byte) 0); // Not sure what this is for + + // Build the position for the update + List posList = new ArrayList<>(); + posList.add(new IntTag("", pos.getX())); + posList.add(new IntTag("", pos.getY())); + posList.add(new IntTag("", pos.getZ())); + + builder.listTag("pos", IntTag.class, posList); + + broadcastPacket.setTag(builder.buildRootTag()); + + session.sendUpstreamPacket(broadcastPacket); + } +} 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 db5bc1b34ba..2dbc945c3d9 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 @@ -133,6 +133,9 @@ public static void init() { 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)); + /* Load creative items */ stream = FileUtils.getResource("bedrock/creative_items.json"); 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 new file mode 100644 index 00000000000..675d42555c0 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/CompassTranslator.java @@ -0,0 +1,121 @@ +/* + * 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.item.translators; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.github.steveice10.opennbt.tag.builtin.*; +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.utils.LoadstoneTracker; + +import java.util.List; +import java.util.stream.Collectors; + +@ItemRemapper +public class CompassTranslator extends ItemTranslator { + + private List appliedItems; + + public CompassTranslator() { + appliedItems = ItemRegistry.ITEM_ENTRIES.values().stream().filter(entry -> entry.getJavaIdentifier().endsWith("compass")).collect(Collectors.toList()); + } + + @Override + public ItemData translateToBedrock(ItemStack itemStack, ItemEntry itemEntry) { + if (itemStack.getNbt() == null) return super.translateToBedrock(itemStack, itemEntry); + + Tag lodestoneTag = itemStack.getNbt().get("LodestoneTracked"); + if (lodestoneTag instanceof ByteTag) { + // Get the fake lodestonecompass entry + itemEntry = ItemRegistry.getItemEntry("minecraft:lodestonecompass"); + + // Get the loadstone pos + CompoundTag loadstonePos = itemStack.getNbt().get("LodestonePos"); + if (loadstonePos != null) { + // Get all info needed for tracking + int x = ((IntTag) loadstonePos.get("X")).getValue(); + int y = ((IntTag) loadstonePos.get("Y")).getValue(); + int z = ((IntTag) loadstonePos.get("Z")).getValue(); + String dim = ((StringTag) itemStack.getNbt().get("LodestoneDimension")).getValue(); + + // Store the info + int trackID = LoadstoneTracker.store(x, y, z, dim); + + // Set the bedrock tracking id + itemStack.getNbt().put(new IntTag("trackingHandle", trackID)); + } else { + // The loadstone was removed just set the tracking id to 0 + itemStack.getNbt().put(new IntTag("trackingHandle", 0)); + } + } + + ItemData itemData = super.translateToBedrock(itemStack, itemEntry); + + return itemData; + } + + @Override + public ItemStack translateToJava(ItemData itemData, ItemEntry itemEntry) { + boolean isLoadstone = false; + if (itemEntry.getJavaIdentifier().equals("minecraft:lodestonecompass")) { + // Revert the entry back to the compass + itemEntry = ItemRegistry.getItemEntry("minecraft:compass"); + + isLoadstone = true; + } + + ItemStack itemStack = super.translateToJava(itemData, itemEntry); + + if (isLoadstone) { + // Get the tracking id + int trackingID = ((IntTag) itemStack.getNbt().get("trackingHandle")).getValue(); + + // Fetch the tracking info from the id + LoadstoneTracker.LoadstonePos pos = LoadstoneTracker.getPos(trackingID); + if (pos != null) { + // Build the new NBT data for the fetched tracking info + itemStack.getNbt().put(new StringTag("LodestoneDimension", pos.getDimension())); + + CompoundTag posTag = new CompoundTag("LodestonePos"); + posTag.put(new IntTag("X", pos.getX())); + posTag.put(new IntTag("Y", pos.getY())); + posTag.put(new IntTag("Z", pos.getZ())); + + itemStack.getNbt().put(posTag); + } + } + + return itemStack; + } + + @Override + public List getAppliedItems() { + return appliedItems; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/utils/LoadstoneTracker.java b/connector/src/main/java/org/geysermc/connector/utils/LoadstoneTracker.java new file mode 100644 index 00000000000..3bd547654a8 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/utils/LoadstoneTracker.java @@ -0,0 +1,84 @@ +/* + * 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 it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; + +public class LoadstoneTracker { + + private static final Int2ObjectMap LOADSTONES = new Int2ObjectOpenHashMap<>(); + + /** + * Store the given coordinates and dimensions + * + * @param x The X position of the Loadstone + * @param y The Y position of the Loadstone + * @param z The Z position of the Loadstone + * @param dim The dimension containing of the Loadstone + * @return The id in the Map + */ + public static int store(int x, int y, int z, String dim) { + LoadstonePos pos = new LoadstonePos(x, y, z, dim); + + if (!LOADSTONES.containsValue(pos)) { + // Start at 1 as 0 seems to not work + LOADSTONES.put(LOADSTONES.size() + 1, pos); + } + + for (Int2ObjectMap.Entry loadstone : LOADSTONES.int2ObjectEntrySet()) { + if (loadstone.getValue().equals(pos)) { + return loadstone.getIntKey(); + } + } + + return 0; + } + + /** + * Get the loadstone data + * + * @param id The ID to get the data for + * @return The stored data + */ + public static LoadstonePos getPos(int id) { + return LOADSTONES.get(id); + } + + @Getter + @AllArgsConstructor + @EqualsAndHashCode + public static class LoadstonePos { + int x; + int y; + int z; + String dimension; + } +} \ No newline at end of file From 95144266d268e50e4cc80a06b14702d3a156394b Mon Sep 17 00:00:00 2001 From: AJ Ferguson Date: Mon, 29 Jun 2020 16:34:07 -0800 Subject: [PATCH 13/72] Handle int tag for enchantment level --- .../item/translators/nbt/EnchantmentTranslator.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/EnchantmentTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/EnchantmentTranslator.java index 404d7824c87..b4846f5c6d8 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/EnchantmentTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/EnchantmentTranslator.java @@ -122,7 +122,7 @@ public void translateToJava(CompoundTag itemTag, ItemEntry itemEntry) { private CompoundTag remapEnchantment(CompoundTag tag) { Tag javaEnchLvl = tag.get("lvl"); - if (!(javaEnchLvl instanceof ShortTag)) + if (!(javaEnchLvl instanceof ShortTag || javaEnchLvl instanceof IntTag)) return null; Tag javaEnchId = tag.get("id"); @@ -137,7 +137,7 @@ private CompoundTag remapEnchantment(CompoundTag tag) { CompoundTag bedrockTag = new CompoundTag(""); bedrockTag.put(new ShortTag("id", (short) enchantment.ordinal())); - bedrockTag.put(new ShortTag("lvl", ((ShortTag) javaEnchLvl).getValue())); + bedrockTag.put(new ShortTag("lvl", ((Number) javaEnchLvl.getValue()).shortValue())); return bedrockTag; } From eb3bde15a76ea25414419593732d0896b41e56d4 Mon Sep 17 00:00:00 2001 From: AJ Ferguson Date: Mon, 29 Jun 2020 16:54:19 -0800 Subject: [PATCH 14/72] Fix stored enchantments accidentally being dropped --- .../translators/item/translators/nbt/EnchantmentTranslator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/EnchantmentTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/EnchantmentTranslator.java index b4846f5c6d8..6bd9cc77ed3 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/EnchantmentTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/EnchantmentTranslator.java @@ -114,7 +114,7 @@ public void translateToJava(CompoundTag itemTag, ItemEntry itemEntry) { itemTag.put(new ListTag("Enchantments", enchantments)); } if (!storedEnchantments.isEmpty()) { - itemTag.put(new ListTag("StoredEnchantments", enchantments)); + itemTag.put(new ListTag("StoredEnchantments", storedEnchantments)); } itemTag.remove("ench"); } From ba6adc988be440639d2d717517c884404bdf274e Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Mon, 29 Jun 2020 21:33:42 -0400 Subject: [PATCH 15/72] Strider mounting fixes; update mappings This commit refactors health visual logic to make it a global system for each living entity. --- .../connector/entity/LivingEntity.java | 38 +++++++++++++++++++ .../entity/living/animal/PigEntity.java | 35 +---------------- .../animal/horse/AbstractHorseEntity.java | 37 ------------------ .../JavaEntitySetPassengersTranslator.java | 6 +++ connector/src/main/resources/mappings | 2 +- 5 files changed, 47 insertions(+), 71 deletions(-) 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 3d1a1456f98..32aa343588b 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/LivingEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/LivingEntity.java @@ -27,15 +27,23 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.AttributeData; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.inventory.ContainerId; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.packet.MobArmorEquipmentPacket; import com.nukkitx.protocol.bedrock.packet.MobEquipmentPacket; +import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket; import lombok.Getter; import lombok.Setter; +import org.geysermc.connector.entity.attribute.AttributeType; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.utils.AttributeUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; @Getter @Setter @@ -98,4 +106,34 @@ public void updateEquipment(GeyserSession session) { session.sendUpstreamPacket(handPacket); session.sendUpstreamPacket(offHandPacket); } + + @Override + public void updateBedrockAttributes(GeyserSession session) { + if (!valid) return; + + float maxHealth = this.attributes.containsKey(AttributeType.MAX_HEALTH) ? this.attributes.get(AttributeType.MAX_HEALTH).getValue() : getDefaultMaxHealth(); + + List attributes = new ArrayList<>(); + for (Map.Entry entry : this.attributes.entrySet()) { + if (!entry.getValue().getType().isBedrockAttribute()) + continue; + + attributes.add(AttributeUtils.getBedrockAttribute(entry.getValue())); + } + // Add health attribute to properly show hearts when mounting + attributes.add(new AttributeData("minecraft:health", 0.0f, maxHealth, metadata.getFloat(EntityData.HEALTH, 20f), maxHealth)); + + UpdateAttributesPacket updateAttributesPacket = new UpdateAttributesPacket(); + updateAttributesPacket.setRuntimeEntityId(geyserId); + updateAttributesPacket.setAttributes(attributes); + session.sendUpstreamPacket(updateAttributesPacket); + } + + /** + * Used for the health visual when mounting an entity. + * @return the default maximum health for the entity. + */ + protected float getDefaultMaxHealth() { + return 20f; + } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/PigEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/PigEntity.java index 0a7c83e7528..9b9701f8a68 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/PigEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/PigEntity.java @@ -27,33 +27,18 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.AttributeData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket; -import org.geysermc.connector.entity.attribute.AttributeType; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.utils.AttributeUtils; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; public class PigEntity extends AnimalEntity { - // For updating the pig heart visual easier - private float health = 20f; - public PigEntity(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() == 8) { - health = (float) entityMetadata.getValue(); - updateBedrockAttributes(session); - } if (entityMetadata.getId() == 16) { metadata.getFlags().setFlag(EntityFlag.SADDLED, (boolean) entityMetadata.getValue()); @@ -62,23 +47,7 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s } @Override - public void updateBedrockAttributes(GeyserSession session) { - if (!valid) return; - - float maxHealth = attributes.containsKey(AttributeType.MAX_HEALTH) ? attributes.get(AttributeType.MAX_HEALTH).getValue() : 20f; - - List attributesLocal = new ArrayList<>(); - for (Map.Entry entry : this.attributes.entrySet()) { - if (!entry.getValue().getType().isBedrockAttribute()) - continue; - - attributesLocal.add(AttributeUtils.getBedrockAttribute(entry.getValue())); - } - attributesLocal.add(new AttributeData("minecraft:health", 0.0f, maxHealth, health, maxHealth)); - - UpdateAttributesPacket updateAttributesPacket = new UpdateAttributesPacket(); - updateAttributesPacket.setRuntimeEntityId(geyserId); - updateAttributesPacket.setAttributes(attributesLocal); - session.sendUpstreamPacket(updateAttributesPacket); + protected float getDefaultMaxHealth() { + return 10f; } } 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 48586c78fd6..e08f9adf001 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,24 +27,13 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.AttributeData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket; -import org.geysermc.connector.entity.attribute.AttributeType; 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.utils.AttributeUtils; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; public class AbstractHorseEntity extends AnimalEntity { - // For updating the horse visual easier - private float health = 20f; - public AbstractHorseEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); } @@ -52,11 +41,6 @@ public AbstractHorseEntity(long entityId, long geyserId, EntityType entityType, @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 8) { - health = (float) entityMetadata.getValue(); - updateBedrockAttributes(session); - } - if (entityMetadata.getId() == 16) { byte xd = (byte) entityMetadata.getValue(); metadata.getFlags().setFlag(EntityFlag.TAMED, (xd & 0x02) == 0x02); @@ -71,25 +55,4 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s super.updateBedrockMetadata(entityMetadata, session); } - - @Override - public void updateBedrockAttributes(GeyserSession session) { - if (!valid) return; - - float maxHealth = attributes.containsKey(AttributeType.MAX_HEALTH) ? attributes.get(AttributeType.MAX_HEALTH).getValue() : 20f; - - List attributesLocal = new ArrayList<>(); - for (Map.Entry entry : this.attributes.entrySet()) { - if (!entry.getValue().getType().isBedrockAttribute()) - continue; - - attributesLocal.add(AttributeUtils.getBedrockAttribute(entry.getValue())); - } - attributesLocal.add(new AttributeData("minecraft:health", 0.0f, maxHealth, health, maxHealth)); - - UpdateAttributesPacket updateAttributesPacket = new UpdateAttributesPacket(); - updateAttributesPacket.setRuntimeEntityId(geyserId); - updateAttributesPacket.setAttributes(attributesLocal); - session.sendUpstreamPacket(updateAttributesPacket); - } } 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 8eab10f5a13..ec3ca8318fd 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 @@ -140,8 +140,14 @@ private void updateOffset(Entity passenger, EntityType mountType, GeyserSession case ARMOR_STAND: yOffset = 1.3f; break; + case STRIDER: + yOffset = 2.8200102f; + 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)); diff --git a/connector/src/main/resources/mappings b/connector/src/main/resources/mappings index fdc65607994..668156d559b 160000 --- a/connector/src/main/resources/mappings +++ b/connector/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit fdc65607994b924261f43170f4d4d563b14167d7 +Subproject commit 668156d559be2c0e081950194342da7fb68f1543 From a9bb8745f5bf305c71d873f294eeb2d9c4ab2fff Mon Sep 17 00:00:00 2001 From: rtm516 Date: Tue, 30 Jun 2020 13:20:03 +0100 Subject: [PATCH 16/72] Fix Zombified Piglin fire flicker --- .../org/geysermc/connector/entity/Entity.java | 2 +- .../entity/living/animal/StriderEntity.java | 2 + .../living/monster/ZombifiedPiglinEntity.java | 39 +++++++++++++++++++ .../connector/entity/type/EntityType.java | 2 +- .../translators/item/ItemRegistry.java | 5 ++- 5 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/entity/living/monster/ZombifiedPiglinEntity.java 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 4d4d097f04a..6ae9c67174d 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/Entity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/Entity.java @@ -260,7 +260,7 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s case 0: if (entityMetadata.getType() == MetadataType.BYTE) { byte xd = (byte) entityMetadata.getValue(); - metadata.getFlags().setFlag(EntityFlag.ON_FIRE, (xd & 0x01) == 0x01); + metadata.getFlags().setFlag(EntityFlag.ON_FIRE, ((xd & 0x01) == 0x01) && !metadata.getFlags().getFlag(EntityFlag.FIRE_IMMUNE)); // Otherwise immune entities sometimes flicker onfire metadata.getFlags().setFlag(EntityFlag.SNEAKING, (xd & 0x02) == 0x02); metadata.getFlags().setFlag(EntityFlag.SPRINTING, (xd & 0x08) == 0x08); metadata.getFlags().setFlag(EntityFlag.SWIMMING, ((xd & 0x10) == 0x10) && metadata.getFlags().getFlag(EntityFlag.SPRINTING)); // Otherwise swimming is enabled on older servers diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/StriderEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/StriderEntity.java index e3ef9b501bb..9d8a6220bdb 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/StriderEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/StriderEntity.java @@ -36,6 +36,8 @@ public class StriderEntity extends AnimalEntity { public StriderEntity(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 diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/ZombifiedPiglinEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/ZombifiedPiglinEntity.java new file mode 100644 index 00000000000..01693170abf --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/ZombifiedPiglinEntity.java @@ -0,0 +1,39 @@ +/* + * 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.EntityFlag; +import org.geysermc.connector.entity.type.EntityType; + +public class ZombifiedPiglinEntity extends ZombieEntity { + + public ZombifiedPiglinEntity(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); + } +} 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 1acf579f1b2..6bc1a1b0bdc 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 @@ -68,7 +68,7 @@ public enum EntityType { CREEPER(CreeperEntity.class, 33, 1.7f, 0.6f, 0.6f, 1.62f), SKELETON(AbstractSkeletonEntity.class, 34, 1.8f, 0.6f, 0.6f, 1.62f), SPIDER(SpiderEntity.class, 35, 0.9f, 1.4f, 1.4f, 1f), - ZOMBIFIED_PIGLIN(ZombieEntity.class, 36, 1.95f, 0.6f, 0.6f, 1.62f, "minecraft:zombie_pigman"), + ZOMBIFIED_PIGLIN(ZombifiedPiglinEntity.class, 36, 1.95f, 0.6f, 0.6f, 1.62f, "minecraft:zombie_pigman"), SLIME(SlimeEntity.class, 37, 0.51f), ENDERMAN(EndermanEntity.class, 38, 2.9f, 0.6f), SILVERFISH(MonsterEntity.class, 39, 0.3f, 0.4f), 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 2dbc945c3d9..aebf1979751 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 @@ -198,7 +198,10 @@ public static ItemEntry getItem(ItemData data) { } } - GeyserConnector.getInstance().getLogger().debug("Missing mapping for bedrock item " + data.getId() + ":" + data.getDamage()); + // This will hide the message when the player clicks with an empty hand + if (data.getId() != 0 && data.getDamage() != 0) { + GeyserConnector.getInstance().getLogger().debug("Missing mapping for bedrock item " + data.getId() + ":" + data.getDamage()); + } return ItemEntry.AIR; } From e7fae53552127521870856dd7096e4eb2374be03 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Tue, 30 Jun 2020 13:51:44 +0100 Subject: [PATCH 17/72] Fix Strider shaking --- .../connector/entity/living/animal/StriderEntity.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/StriderEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/StriderEntity.java index 9d8a6220bdb..bb4daf545ff 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/StriderEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/StriderEntity.java @@ -38,12 +38,14 @@ public StriderEntity(long entityId, long geyserId, EntityType entityType, Vector super(entityId, geyserId, entityType, position, motion, rotation); metadata.getFlags().setFlag(EntityFlag.FIRE_IMMUNE, true); + metadata.getFlags().setFlag(EntityFlag.BREATHING, true); } @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { if (entityMetadata.getId() == 17) { - metadata.getFlags().setFlag(EntityFlag.ALWAYS_SHOW_NAME, (boolean) entityMetadata.getValue()); + metadata.getFlags().setFlag(EntityFlag.BREATHING, !(boolean) entityMetadata.getValue()); + metadata.getFlags().setFlag(EntityFlag.SHAKING, (boolean) entityMetadata.getValue()); } if (entityMetadata.getId() == 18) { metadata.getFlags().setFlag(EntityFlag.SADDLED, (boolean) entityMetadata.getValue()); From 4c2a8789afae978b79af3fa5a1e3324aab751029 Mon Sep 17 00:00:00 2001 From: D3ATHBRINGER13 <53559772+D3ATHBRINGER13@users.noreply.github.com> Date: Tue, 30 Jun 2020 16:22:39 +0100 Subject: [PATCH 18/72] Update Bedrock Version (#868) * Update Bedrock Version * Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5cf34473de1..a22a7a8d073 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have now joined us here! -### Currently supporting Minecraft Bedrock v1.16.0 and Minecraft Java v1.16.1. +### Currently supporting Minecraft Bedrock v1.16.0/1 and Minecraft Java v1.16.1. ## Setting Up Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set up Geyser. From ed3fbf3ee0b2d71b3062bec316c901fc41abe2a8 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Tue, 30 Jun 2020 11:27:28 -0400 Subject: [PATCH 19/72] Add message about 'outdated' Geyser. (#877) Ensures people are aware of a 1.16 build. --- .../src/main/java/org/geysermc/connector/GeyserConnector.java | 1 + 1 file changed, 1 insertion(+) diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index d201656ad25..3d55845dc43 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -110,6 +110,7 @@ private GeyserConnector(PlatformType platformType, GeyserBootstrap bootstrap) { logger.info("Loading " + NAME + " version " + VERSION); logger.info(""); logger.info("******************************************"); + logger.warning("This build is for connecting to Java 1.15.2 servers and from Bedrock 1.14.60 clients. Please join our Discord (https://discord.geysermc.org) for up-to-date information about where to download 1.16."); this.generalThreadPool = Executors.newScheduledThreadPool(config.getGeneralThreadPool()); From c804a6edfb4ccc1348ec18868dfdba9d135961a2 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Tue, 30 Jun 2020 17:08:22 +0100 Subject: [PATCH 20/72] Fix respawning and death not being registered on the client --- .../translators/java/JavaRespawnTranslator.java | 5 ++--- .../java/entity/JavaEntityStatusTranslator.java | 14 ++++++++++++++ .../JavaPlayerPositionRotationTranslator.java | 12 ++++++++++++ 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaRespawnTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaRespawnTranslator.java index 288389fa646..6d03380821f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaRespawnTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaRespawnTranslator.java @@ -25,9 +25,11 @@ package org.geysermc.connector.network.translators.java; +import com.github.steveice10.mc.protocol.packet.ingame.server.ServerRespawnPacket; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.LevelEventType; import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; +import com.nukkitx.protocol.bedrock.packet.SetPlayerGameTypePacket; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.attribute.AttributeType; import org.geysermc.connector.network.session.GeyserSession; @@ -35,9 +37,6 @@ import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.utils.DimensionUtils; -import com.github.steveice10.mc.protocol.packet.ingame.server.ServerRespawnPacket; -import com.nukkitx.protocol.bedrock.packet.SetPlayerGameTypePacket; - import java.util.concurrent.ThreadLocalRandom; @Translator(packet = ServerRespawnPacket.class) 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 98089ff277d..0af473ff763 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 @@ -28,11 +28,14 @@ import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityStatusPacket; import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; +import org.geysermc.connector.GeyserConnector; 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 java.util.concurrent.TimeUnit; + @Translator(packet = ServerEntityStatusPacket.class) public class JavaEntityStatusTranslator extends PacketTranslator { @@ -99,5 +102,16 @@ public void translate(ServerEntityStatusPacket packet, GeyserSession session) { } session.sendUpstreamPacket(entityEventPacket); + + // This fixes some death events not getting registered on the client such as drowning + if (entityEventPacket.getType() == EntityEventType.DEATH) { + GeyserConnector.getInstance().getGeneralThreadPool().schedule(() -> { + EntityEventPacket eventPacket2 = new EntityEventPacket(); + eventPacket2.setRuntimeEntityId(entityEventPacket.getRuntimeEntityId()); + eventPacket2.setType(entityEventPacket.getType()); + eventPacket2.setData(entityEventPacket.getData()); + session.sendUpstreamPacket(eventPacket2); + }, 1, TimeUnit.MILLISECONDS); + } } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerPositionRotationTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerPositionRotationTranslator.java index 8b0b8201487..fae289e12ae 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerPositionRotationTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerPositionRotationTranslator.java @@ -34,6 +34,7 @@ import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket; import com.nukkitx.protocol.bedrock.packet.RespawnPacket; import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket; +import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.entity.PlayerEntity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; @@ -42,6 +43,8 @@ import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.utils.ChunkUtils; +import java.util.concurrent.TimeUnit; + @Translator(packet = ServerPlayerPositionRotationPacket.class) public class JavaPlayerPositionRotationTranslator extends PacketTranslator { @@ -71,6 +74,15 @@ public void translate(ServerPlayerPositionRotationPacket packet, GeyserSession s eventPacket.setData(0); session.sendUpstreamPacket(eventPacket); + // This will prevent the client getting stuck at the respawn screen + GeyserConnector.getInstance().getGeneralThreadPool().schedule(() -> { + EntityEventPacket eventPacket2 = new EntityEventPacket(); + eventPacket2.setRuntimeEntityId(entity.getGeyserId()); + eventPacket2.setType(EntityEventType.RESPAWN); + eventPacket2.setData(0); + session.sendUpstreamPacket(eventPacket2); + }, 1, TimeUnit.MILLISECONDS); + SetEntityDataPacket entityDataPacket = new SetEntityDataPacket(); entityDataPacket.setRuntimeEntityId(entity.getGeyserId()); entityDataPacket.getMetadata().putAll(entity.getMetadata()); From 81651cfac5daa77f7377e6e9b3f7de36fa24b43e Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Tue, 30 Jun 2020 20:39:21 -0400 Subject: [PATCH 21/72] Add support for 3D biomes; fix Nether biome display --- .../network/translators/BiomeTranslator.java | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/BiomeTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/BiomeTranslator.java index c12cc51361d..6f858eb501d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/BiomeTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/BiomeTranslator.java @@ -70,24 +70,27 @@ public static byte[] toBedrockBiome(int[] biomeData) { return bedrockData; } - for (int z = 0; z < 16; z += 4) { - for (int x = 0; x < 16; x += 4) { - byte biomeId = biomeID(biomeData, x, z); - fillArray(z, x, bedrockData, biomeId); - fillArray(z + 1, x, bedrockData, biomeId); - fillArray(z + 2, x, bedrockData, biomeId); - fillArray(z + 3, x, bedrockData, biomeId); + for (int y = 0; y < 16; y += 4) { + for (int z = 0; z < 16; z += 4) { + for (int x = 0; x < 16; x += 4) { + byte biomeId = biomeID(biomeData, x, y, z); + int offset = ((z + (y / 4)) << 4) | x; + Arrays.fill(bedrockData, offset, offset + 4, biomeId); + } } } return bedrockData; } - private static void fillArray(int z, int x, byte[] legacyBiomeData, int biomeId) { - int offset = (z << 4) | x; - Arrays.fill(legacyBiomeData, offset, offset + 4, (byte) biomeId); - } - - private static byte biomeID(int[] biomeData, int x, int z) { - return (byte) biomeData[((z >> 2) & 3) << 2 | ((x >> 2) & 3)]; + private static byte biomeID(int[] biomeData, int x, int y, int z) { + int biomeId = biomeData[((y >> 2) & 63) << 4 | ((z >> 2) & 3) << 2 | ((x >> 2) & 3)]; + if (biomeId == 0) { + biomeId = 42; // Ocean + } else if (biomeId >= 40 && biomeId <= 43) { // Java has multiple End dimensions that Bedrock doesn't recognize + biomeId = 9; + } else if (biomeId >= 170) { // Nether biomes. Dunno why it's like this :microjang: + biomeId = biomeId + 8; + } + return (byte) biomeId; } } From 51dfda1c917baba9fde170df5e0ce30307ebadba Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Wed, 1 Jul 2020 08:22:21 -0400 Subject: [PATCH 22/72] Clean up formatting --- .../connector/network/translators/BiomeTranslator.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/BiomeTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/BiomeTranslator.java index 6f858eb501d..880c1c9cd32 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/BiomeTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/BiomeTranslator.java @@ -55,7 +55,7 @@ public static void init() { CompoundTag biomesTag; - try (NBTInputStream biomenbtInputStream = NbtUtils.createNetworkReader(stream)){ + try (NBTInputStream biomenbtInputStream = NbtUtils.createNetworkReader(stream)) { biomesTag = (CompoundTag) biomenbtInputStream.readTag(); BIOMES = biomesTag; } catch (Exception ex) { @@ -89,7 +89,7 @@ private static byte biomeID(int[] biomeData, int x, int y, int z) { } else if (biomeId >= 40 && biomeId <= 43) { // Java has multiple End dimensions that Bedrock doesn't recognize biomeId = 9; } else if (biomeId >= 170) { // Nether biomes. Dunno why it's like this :microjang: - biomeId = biomeId + 8; + biomeId += 8; } return (byte) biomeId; } From 0f342c1e809222d3bbee3873f0d577f6f835a397 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Wed, 1 Jul 2020 14:26:00 +0100 Subject: [PATCH 23/72] Update mappings to fix fences and plant stems --- connector/src/main/resources/mappings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/src/main/resources/mappings b/connector/src/main/resources/mappings index 668156d559b..b443f3d43ff 160000 --- a/connector/src/main/resources/mappings +++ b/connector/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit 668156d559be2c0e081950194342da7fb68f1543 +Subproject commit b443f3d43ff2da104816490e3190a3194ee610b2 From c17f21eedc258e148a097435c6824e7c7846a471 Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Wed, 1 Jul 2020 12:28:03 -0400 Subject: [PATCH 24/72] Fix respawn bugs - hopefully for good Thanks to @bundabrg for spotting this one. --- .../connector/entity/LivingEntity.java | 8 +++++-- .../connector/entity/PlayerEntity.java | 22 +++++++++++++++++++ .../bedrock/BedrockRespawnTranslator.java | 7 +++--- .../entity/JavaEntityStatusTranslator.java | 14 ------------ .../JavaPlayerPositionRotationTranslator.java | 12 ---------- 5 files changed, 31 insertions(+), 32 deletions(-) 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 32aa343588b..07a09fa1f68 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/LivingEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/LivingEntity.java @@ -117,11 +117,15 @@ public void updateBedrockAttributes(GeyserSession session) { for (Map.Entry entry : this.attributes.entrySet()) { if (!entry.getValue().getType().isBedrockAttribute()) continue; + if (entry.getValue().getType() == AttributeType.HEALTH) { + // Add health attribute to properly show hearts when mounting + // TODO: Not a perfect system, since it led to respawn bugs + attributes.add(new AttributeData("minecraft:health", 0.0f, maxHealth, metadata.getFloat(EntityData.HEALTH, 20f), maxHealth)); + continue; + } attributes.add(AttributeUtils.getBedrockAttribute(entry.getValue())); } - // Add health attribute to properly show hearts when mounting - attributes.add(new AttributeData("minecraft:health", 0.0f, maxHealth, metadata.getFloat(EntityData.HEALTH, 20f), maxHealth)); UpdateAttributesPacket updateAttributesPacket = new UpdateAttributesPacket(); updateAttributesPacket.setRuntimeEntityId(geyserId); 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 c567aa078e3..fe4dc905c78 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java @@ -39,15 +39,19 @@ import lombok.Getter; import lombok.Setter; import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.entity.attribute.Attribute; +import org.geysermc.connector.entity.attribute.AttributeType; import org.geysermc.connector.entity.type.EntityType; 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.utils.SkinUtils; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.UUID; import java.util.concurrent.TimeUnit; @@ -293,4 +297,22 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s } } } + + @Override + public void updateBedrockAttributes(GeyserSession session) { // TODO: Don't use duplicated code + if (!valid) return; + + List attributes = new ArrayList<>(); + for (Map.Entry entry : this.attributes.entrySet()) { + if (!entry.getValue().getType().isBedrockAttribute()) + continue; + + attributes.add(AttributeUtils.getBedrockAttribute(entry.getValue())); + } + + UpdateAttributesPacket updateAttributesPacket = new UpdateAttributesPacket(); + updateAttributesPacket.setRuntimeEntityId(geyserId); + updateAttributesPacket.setAttributes(attributes); + session.sendUpstreamPacket(updateAttributesPacket); + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockRespawnTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockRespawnTranslator.java index 8fc377ab50b..14bc1507e16 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockRespawnTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockRespawnTranslator.java @@ -25,14 +25,13 @@ package org.geysermc.connector.network.translators.bedrock; -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.ClientRequest; import com.github.steveice10.mc.protocol.packet.ingame.client.ClientRequestPacket; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.packet.RespawnPacket; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; @Translator(packet = RespawnPacket.class) public class BedrockRespawnTranslator extends PacketTranslator { 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 0af473ff763..98089ff277d 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 @@ -28,14 +28,11 @@ import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityStatusPacket; import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; -import org.geysermc.connector.GeyserConnector; 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 java.util.concurrent.TimeUnit; - @Translator(packet = ServerEntityStatusPacket.class) public class JavaEntityStatusTranslator extends PacketTranslator { @@ -102,16 +99,5 @@ public void translate(ServerEntityStatusPacket packet, GeyserSession session) { } session.sendUpstreamPacket(entityEventPacket); - - // This fixes some death events not getting registered on the client such as drowning - if (entityEventPacket.getType() == EntityEventType.DEATH) { - GeyserConnector.getInstance().getGeneralThreadPool().schedule(() -> { - EntityEventPacket eventPacket2 = new EntityEventPacket(); - eventPacket2.setRuntimeEntityId(entityEventPacket.getRuntimeEntityId()); - eventPacket2.setType(entityEventPacket.getType()); - eventPacket2.setData(entityEventPacket.getData()); - session.sendUpstreamPacket(eventPacket2); - }, 1, TimeUnit.MILLISECONDS); - } } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerPositionRotationTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerPositionRotationTranslator.java index fae289e12ae..8b0b8201487 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerPositionRotationTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerPositionRotationTranslator.java @@ -34,7 +34,6 @@ import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket; import com.nukkitx.protocol.bedrock.packet.RespawnPacket; import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket; -import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.entity.PlayerEntity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; @@ -43,8 +42,6 @@ import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.utils.ChunkUtils; -import java.util.concurrent.TimeUnit; - @Translator(packet = ServerPlayerPositionRotationPacket.class) public class JavaPlayerPositionRotationTranslator extends PacketTranslator { @@ -74,15 +71,6 @@ public void translate(ServerPlayerPositionRotationPacket packet, GeyserSession s eventPacket.setData(0); session.sendUpstreamPacket(eventPacket); - // This will prevent the client getting stuck at the respawn screen - GeyserConnector.getInstance().getGeneralThreadPool().schedule(() -> { - EntityEventPacket eventPacket2 = new EntityEventPacket(); - eventPacket2.setRuntimeEntityId(entity.getGeyserId()); - eventPacket2.setType(EntityEventType.RESPAWN); - eventPacket2.setData(0); - session.sendUpstreamPacket(eventPacket2); - }, 1, TimeUnit.MILLISECONDS); - SetEntityDataPacket entityDataPacket = new SetEntityDataPacket(); entityDataPacket.setRuntimeEntityId(entity.getGeyserId()); entityDataPacket.getMetadata().putAll(entity.getMetadata()); From 699ae0b88ed45deefbdf60ddd7d86c81e86df495 Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Wed, 1 Jul 2020 20:27:39 -0400 Subject: [PATCH 25/72] Set strider entity offset properly if not a player entity --- .../java/entity/JavaEntitySetPassengersTranslator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 ec3ca8318fd..87491fbf317 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 @@ -141,7 +141,7 @@ private void updateOffset(Entity passenger, EntityType mountType, GeyserSession yOffset = 1.3f; break; case STRIDER: - yOffset = 2.8200102f; + yOffset = passenger.getEntityType() == EntityType.PLAYER ? 2.8200102f : 1.6f; break; } Vector3f offset = Vector3f.from(0f, yOffset, 0f); From 61072948b9f514eadcba92f7cd0f44e90ceb000e Mon Sep 17 00:00:00 2001 From: rtm516 Date: Thu, 2 Jul 2020 20:10:43 -0400 Subject: [PATCH 26/72] Add GUI to standalone --- bootstrap/standalone/pom.xml | 2 +- .../standalone/GeyserStandaloneBootstrap.java | 56 ++- .../standalone/GeyserStandaloneLogger.java | 11 +- .../platform/standalone/gui/ANSIColor.java | 79 ++++ .../platform/standalone/gui/ColorPane.java | 118 ++++++ .../standalone/gui/GeyserStandaloneGUI.java | 336 ++++++++++++++++++ .../platform/standalone/gui/GraphPanel.java | 188 ++++++++++ .../standalone/src/main/resources/icon.png | Bin 0 -> 115461 bytes .../standalone/src/main/resources/log4j2.xml | 6 +- .../geysermc/connector/GeyserConnector.java | 21 +- 10 files changed, 807 insertions(+), 10 deletions(-) create mode 100644 bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/ANSIColor.java create mode 100644 bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/ColorPane.java create mode 100644 bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/GeyserStandaloneGUI.java create mode 100644 bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/GraphPanel.java create mode 100644 bootstrap/standalone/src/main/resources/icon.png diff --git a/bootstrap/standalone/pom.xml b/bootstrap/standalone/pom.xml index 770ca1009ed..984a6baad22 100644 --- a/bootstrap/standalone/pom.xml +++ b/bootstrap/standalone/pom.xml @@ -20,7 +20,7 @@ net.minecrell terminalconsoleappender - 1.1.1 + 1.2.0 org.apache.logging.log4j 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 3fb561a1d22..762b09ba5fc 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,16 +25,23 @@ package org.geysermc.platform.standalone; -import org.geysermc.connector.common.PlatformType; +import lombok.Getter; +import net.minecrell.terminalconsole.TerminalConsoleAppender; +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.connector.GeyserConnector; import org.geysermc.connector.bootstrap.GeyserBootstrap; -import org.geysermc.connector.configuration.GeyserConfiguration; +import org.geysermc.connector.common.PlatformType; import org.geysermc.connector.command.CommandManager; +import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.dump.BootstrapDumpInfo; -import org.geysermc.connector.ping.IGeyserPingPassthrough; import org.geysermc.connector.ping.GeyserLegacyPingPassthrough; +import org.geysermc.connector.ping.IGeyserPingPassthrough; import org.geysermc.connector.utils.FileUtils; import org.geysermc.platform.standalone.command.GeyserCommandManager; +import org.geysermc.platform.standalone.gui.GeyserStandaloneGUI; import java.io.File; import java.io.IOException; @@ -49,14 +56,49 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { private GeyserStandaloneLogger geyserLogger; private IGeyserPingPassthrough geyserPingPassthrough; + private GeyserStandaloneGUI gui; + + @Getter + private boolean useGui = System.console() == null; + private GeyserConnector connector; public static void main(String[] args) { + for (String arg : args) { + // 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 + if (arg.equals("gui")) { + new GeyserStandaloneBootstrap().onEnable(true); + return; + } else if (arg.equals("nogui")) { + new GeyserStandaloneBootstrap().onEnable(false); + return; + } + } new GeyserStandaloneBootstrap().onEnable(); } + public void onEnable(boolean useGui) { + this.useGui = useGui; + this.onEnable(); + } + @Override public void onEnable() { + Logger logger = (Logger) LogManager.getRootLogger(); + for (Appender appender : logger.getAppenders().values()) { + // Remove the appender that is not in use + // Prevents multiple appenders/double logging and removes harmless errors + if ((useGui && appender instanceof TerminalConsoleAppender) || (!useGui && appender instanceof ConsoleAppender)) { + logger.removeAppender(appender); + } + } + if (useGui && gui == null) { + gui = new GeyserStandaloneGUI(); + gui.redirectSystemStreams(); + gui.startUpdateThread(); + } + geyserLogger = new GeyserStandaloneLogger(); LoopbackUtil.checkLoopback(geyserLogger); @@ -73,9 +115,15 @@ public void onEnable() { connector = GeyserConnector.start(PlatformType.STANDALONE, this); geyserCommandManager = new GeyserCommandManager(connector); + if (gui != null) { + gui.setupInterface(geyserLogger, geyserCommandManager); + } + geyserPingPassthrough = GeyserLegacyPingPassthrough.init(connector); - geyserLogger.start(); + if (!useGui) { + geyserLogger.start(); // Throws an error otherwise + } } @Override diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneLogger.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneLogger.java index ae7f1871841..ffc322dd5a6 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneLogger.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneLogger.java @@ -30,6 +30,7 @@ import net.minecrell.terminalconsole.SimpleTerminalConsole; +import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.config.Configurator; import org.geysermc.connector.common.ChatColor; import org.geysermc.connector.GeyserConnector; @@ -96,7 +97,15 @@ public static String printConsole(String message, boolean colors) { @Override public void setDebug(boolean debug) { - Configurator.setLevel(log.getName(), debug ? org.apache.logging.log4j.Level.DEBUG : log.getLevel()); + Configurator.setLevel(log.getName(), debug ? Level.DEBUG : Level.INFO); + } + + /** + * Used for setting debug mode in GUI mode + * @return if debug is enabled + */ + public boolean isDebug() { + return log.isDebugEnabled(); } @Override diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/ANSIColor.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/ANSIColor.java new file mode 100644 index 00000000000..1f4ff998a5e --- /dev/null +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/ANSIColor.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.standalone.gui; + +import lombok.Getter; + +import java.awt.*; +import java.util.regex.Pattern; + +public enum ANSIColor { + // Normal colors + BLACK("(0;)?30(0;)?m", Color.getHSBColor(0.000f, 0.000f, 0.000f)), + RED("(0;)?31(0;)?m", Color.getHSBColor(0.000f, 1.000f, 0.502f)), + GREEN("(0;)?32(0;)?m", Color.getHSBColor(0.333f, 1.000f, 0.502f)), + YELLOW("(0;)?33(0;)?m", Color.getHSBColor(0.167f, 1.000f, 0.502f)), + BLUE("(0;)?34(0;)?m", Color.getHSBColor(0.667f, 1.000f, 0.502f)), + MAGENTA("(0;)?35(0;)?m", Color.getHSBColor(0.833f, 1.000f, 0.502f)), + CYAN("(0;)?36(0;)?m", Color.getHSBColor(0.500f, 1.000f, 0.502f)), + WHITE("(0;)?37(0;)?m", Color.getHSBColor(0.000f, 0.000f, 0.753f)), + + // Bold colors + B_BLACK("(1;30|30;1)m", Color.getHSBColor(0.000f, 0.000f, 0.502f)), + B_RED("(1;31|31;1)m", Color.getHSBColor(0.000f, 1.000f, 1.000f)), + B_GREEN("(1;32|32;1)m", Color.getHSBColor(0.333f, 1.000f, 1.000f)), + B_YELLOW("(1;33|33;1)m", Color.getHSBColor(0.167f, 1.000f, 1.000f)), + B_BLUE("(1;34|34;1)m", Color.getHSBColor(0.667f, 1.000f, 1.000f)), + B_MAGENTA("(1;35|35;1)m", Color.getHSBColor(0.833f, 1.000f, 1.000f)), + B_CYAN("(1;36|36;1)m", Color.getHSBColor(0.500f, 1.000f, 1.000f)), + B_WHITE("(1;37|37;1)m", Color.getHSBColor(0.000f, 0.000f, 1.000f)), + + RESET("0m", Color.getHSBColor(0.000f, 0.000f, 1.000f)); + + private static final ANSIColor[] VALUES = values(); + private static final String PREFIX = Pattern.quote("\u001B["); + + private final String ANSICode; + + @Getter + private final Color color; + + ANSIColor(String ANSICode, Color color) { + this.ANSICode = ANSICode; + this.color = color; + } + + public static ANSIColor fromANSI(String code) { + for (ANSIColor value : VALUES) { + if (code.matches(PREFIX + value.ANSICode)) { + return value; + } + } + + return B_WHITE; + } +} diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/ColorPane.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/ColorPane.java new file mode 100644 index 00000000000..f4586bbb12d --- /dev/null +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/ColorPane.java @@ -0,0 +1,118 @@ +/* + * 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.standalone.gui; + +import javax.swing.*; +import javax.swing.text.*; +import java.awt.*; + +/** + * This class was based on this code: https://stackoverflow.com/a/6899478/5299903 + */ +public class ColorPane extends JTextPane { + private static Color colorCurrent = ANSIColor.RESET.getColor(); + private String remaining = ""; + + /** + * Append the given string in the given color to the text pane + * @param c The color + * @param s The text + */ + private void append(Color c, String s) { + StyleContext sc = StyleContext.getDefaultStyleContext(); + AttributeSet aset = sc.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, c); + int len = getDocument().getLength(); + + try { + getDocument().insertString(len, s, aset); + } catch (BadLocationException e) { + e.printStackTrace(); + } + } + + /** + * Extract the ANSI color codes from the string and add each part to the text pane + * + * @param s The text to parse + */ + public void appendANSI(String s) { // convert ANSI color codes first + int aPos = 0; // current char position in addString + int aIndex = 0; // index of next Escape sequence + int mIndex = 0; // index of "m" terminating Escape sequence + String tmpString = ""; + boolean stillSearching = true; // true until no more Escape sequences + String addString = remaining + s; + remaining = ""; + + if (addString.length() > 0) { + aIndex = addString.indexOf("\u001B"); // find first escape + if (aIndex == -1) { // no escape/color change in this string, so just send it with current color + append(colorCurrent, addString); + return; + } + // otherwise There is an escape character in the string, so we must process it + + if (aIndex > 0) { // Escape is not first char, so send text up to first escape + tmpString = addString.substring(0, aIndex); + append(colorCurrent, tmpString); + aPos = aIndex; // aPos is now at the beginning of the first escape sequence + } + + + // while there's text in the input buffer + stillSearching = true; + while (stillSearching) { + mIndex = addString.indexOf("m", aPos); // find the end of the escape sequence + if (mIndex < 0) { // the buffer ends halfway through the ansi string! + remaining = addString.substring(aPos, addString.length()); + stillSearching = false; + continue; + } else { + tmpString = addString.substring(aPos, mIndex+1); + colorCurrent = ANSIColor.fromANSI(tmpString).getColor(); + } + aPos = mIndex + 1; + // now we have the color, send text that is in that color (up to next escape) + + aIndex = addString.indexOf("\u001B", aPos); + + if (aIndex == -1) { // if that was the last sequence of the input, send remaining text + tmpString = addString.substring(aPos, addString.length()); + append(colorCurrent, tmpString); + stillSearching = false; + continue; // jump out of loop early, as the whole string has been sent now + } + + // there is another escape sequence, so send part of the string and prepare for the next + tmpString = addString.substring(aPos, aIndex); + aPos = aIndex; + append(colorCurrent, tmpString); + + } + } + } +} 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 new file mode 100644 index 00000000000..cc40ae5374b --- /dev/null +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/GeyserStandaloneGUI.java @@ -0,0 +1,336 @@ +/* + * 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.standalone.gui; + +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.command.GeyserCommand; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.platform.standalone.GeyserStandaloneLogger; +import org.geysermc.platform.standalone.command.GeyserCommandManager; + +import javax.swing.*; +import javax.swing.table.DefaultTableModel; +import javax.swing.text.Document; +import java.awt.*; +import java.awt.event.*; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.net.InetSocketAddress; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +public class GeyserStandaloneGUI { + + private static final String[] playerTableHeadings = new String[] {"IP", "Username"}; + private static final List ramValues = new ArrayList<>(); + + private static final ColorPane consolePane = new ColorPane(); + private static final GraphPanel ramGraph = new GraphPanel(); + private static final JTable playerTable = new JTable(new String[][] { }, playerTableHeadings); + private static final int originalFontSize = consolePane.getFont().getSize(); + + private static final long MEGABYTE = 1024L * 1024L; + + private final JMenu commandsMenu; + private final JMenu optionsMenu; + + public GeyserStandaloneGUI() { + // Create the frame and setup basic settings + JFrame frame = new JFrame("Geyser Standalone"); + frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + frame.setSize(800, 400); + frame.setMinimumSize(frame.getSize()); + + // Remove Java UI look + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (Exception ignored) { } + + // Show a confirm dialog on close + frame.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent we) + { + String[] buttons = {"Yes", "No"}; + int result = JOptionPane.showOptionDialog(frame, "Are you sure you want to exit?", frame.getTitle(), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null, buttons, buttons[1]); + if (result == JOptionPane.YES_OPTION) { + System.exit(0); + } + } + }); + + Container cp = frame.getContentPane(); + + // Fetch and set the icon for the frame + URL image = getClass().getClassLoader().getResource("icon.png"); + if (image != null) { + ImageIcon icon = new ImageIcon(image); + frame.setIconImage(icon.getImage()); + } + + // Setup the split pane and event listeners + JSplitPane splitPane = new JSplitPane(); + splitPane.setDividerLocation(600); + splitPane.addPropertyChangeListener("dividerLocation", e -> splitPaneLimit((JSplitPane)e.getSource())); + splitPane.addComponentListener(new ComponentAdapter() { + public void componentResized(ComponentEvent e) { + splitPaneLimit((JSplitPane)e.getSource()); + } + }); + + cp.add(splitPane, BorderLayout.CENTER); + + // Set the background and disable input for the text pane + consolePane.setBackground(Color.BLACK); + consolePane.setEditable(false); + + // Wrap the text pane in a scroll pane and add it to the form + JScrollPane consoleScrollPane = new JScrollPane(consolePane); + //cp.add(consoleScrollPane, BorderLayout.CENTER); + splitPane.setLeftComponent(consoleScrollPane); + + // Create a new menu bar for the top of the frame + JMenuBar menuBar = new JMenuBar(); + + // Create 'File' + JMenu fileMenu = new JMenu("File"); + fileMenu.setMnemonic(KeyEvent.VK_F); + menuBar.add(fileMenu); + + // 'Open Geyser folder' button + JMenuItem openButton = new JMenuItem("Open Geyser folder", KeyEvent.VK_O); + openButton.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, InputEvent.CTRL_MASK)); + openButton.addActionListener(e -> { + try { + Desktop.getDesktop().open(new File("./")); + } catch (IOException ignored) { } + }); + fileMenu.add(openButton); + + fileMenu.addSeparator(); + + // 'Exit' button + JMenuItem exitButton = new JMenuItem("Exit", KeyEvent.VK_X); + exitButton.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F4, InputEvent.ALT_MASK)); + exitButton.addActionListener(e -> System.exit(0)); + fileMenu.add(exitButton); + + // Create 'Commands' + commandsMenu = new JMenu("Commands"); + commandsMenu.setMnemonic(KeyEvent.VK_C); + menuBar.add(commandsMenu); + + // Create 'View' + JMenu viewMenu = new JMenu("View"); + viewMenu.setMnemonic(KeyEvent.VK_V); + menuBar.add(viewMenu); + + // 'Zoom in' button + JMenuItem zoomInButton = new JMenuItem("Zoom In"); + zoomInButton.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_EQUALS, InputEvent.CTRL_DOWN_MASK)); + zoomInButton.addActionListener(e -> consolePane.setFont(new Font(consolePane.getFont().getName(), consolePane.getFont().getStyle(), consolePane.getFont().getSize() + 1))); + viewMenu.add(zoomInButton); + + // 'Zoom in' button + JMenuItem zoomOutButton = new JMenuItem("Zoom Out"); + zoomOutButton.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, InputEvent.CTRL_DOWN_MASK)); + zoomOutButton.addActionListener(e -> consolePane.setFont(new Font(consolePane.getFont().getName(), consolePane.getFont().getStyle(), consolePane.getFont().getSize() - 1))); + viewMenu.add(zoomOutButton); + + // 'Reset Zoom' button + JMenuItem resetZoomButton = new JMenuItem("Reset Zoom"); + resetZoomButton.addActionListener(e -> consolePane.setFont(new Font(consolePane.getFont().getName(), consolePane.getFont().getStyle(), originalFontSize))); + viewMenu.add(resetZoomButton); + + // create 'Options' + optionsMenu = new JMenu("Options"); + viewMenu.setMnemonic(KeyEvent.VK_O); + menuBar.add(optionsMenu); + + // Set the frames menu bar + frame.setJMenuBar(menuBar); + + JPanel rightPane = new JPanel(); + rightPane.setLayout(new CardLayout(5, 5)); + //cp.add(rightPane, BorderLayout.EAST); + splitPane.setRightComponent(rightPane); + + JPanel rightContentPane = new JPanel(); + rightContentPane.setLayout(new GridLayout(2, 1, 5, 5)); + rightPane.add(rightContentPane); + + // Set the ram graph to 0 + for (int i = 0; i < 10; i++) { + ramValues.add(0); + } + ramGraph.setValues(ramValues); + ramGraph.setXLabel("Loading..."); + rightContentPane.add(ramGraph); + + JScrollPane playerScrollPane = new JScrollPane(playerTable); + rightContentPane.add(playerScrollPane); + + // This has to be done last + frame.setVisible(true); + } + + /** + * Queue up an update to the text pane so we don't block the main thread + * + * @param text The text to append + */ + private void updateTextPane(final String text) { + SwingUtilities.invokeLater(() -> { + consolePane.appendANSI(text); + Document doc = consolePane.getDocument(); + consolePane.setCaretPosition(doc.getLength()); + }); + } + + /** + * Redirect the default io streams to the text pane + */ + public void redirectSystemStreams() { + // Setup a new output stream to forward it to the text pane + OutputStream out = new OutputStream() { + @Override + public void write(final int b) { + updateTextPane(String.valueOf((char) b)); + } + + @Override + public void write(byte[] b, int off, int len) { + updateTextPane(new String(b, off, len)); + } + + @Override + public void write(byte[] b) { + write(b, 0, b.length); + } + }; + + // Override the system output streams + System.setOut(new PrintStream(out, true)); + System.setErr(new PrintStream(out, true)); + + } + + /** + * Add all the Geyser commands to the commands menu, and setup the debug mode toggle + * + * @param geyserStandaloneLogger The current logger + * @param geyserCommandManager The commands manager + */ + public void setupInterface(GeyserStandaloneLogger geyserStandaloneLogger, GeyserCommandManager geyserCommandManager) { + commandsMenu.removeAll(); + + 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())) { + continue; + } + + // Create the button that runs the command + JMenuItem commandButton = new JMenuItem(command.getValue().getName()); + commandButton.getAccessibleContext().setAccessibleDescription(command.getValue().getDescription()); + commandButton.addActionListener(e -> command.getValue().execute(geyserStandaloneLogger, new String[]{ })); + commandsMenu.add(commandButton); + } + + // 'Debug Mode' toggle + JCheckBoxMenuItem debugMode = new JCheckBoxMenuItem("Debug Mode"); + debugMode.setSelected(geyserStandaloneLogger.isDebug()); + debugMode.addActionListener(e -> geyserStandaloneLogger.setDebug(!geyserStandaloneLogger.isDebug())); + optionsMenu.add(debugMode); + } + + /** + * Start the thread to update the form information every 1s + */ + public void startUpdateThread() { + ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); + + Runnable periodicTask = () -> { + if (GeyserConnector.getInstance() != null) { + // Update player table + String[][] playerNames = new String[GeyserConnector.getInstance().getPlayers().size()][2]; + int i = 0; + for (Map.Entry player : GeyserConnector.getInstance().getPlayers().entrySet()) { + playerNames[i][0] = player.getKey().getHostName(); + playerNames[i][1] = player.getValue().getPlayerEntity().getUsername(); + + i++; + } + + DefaultTableModel model = new DefaultTableModel(playerNames, playerTableHeadings); + playerTable.setModel(model); + model.fireTableDataChanged(); + } + + // Update ram graph + final long freeMemory = Runtime.getRuntime().freeMemory(); + final long totalMemory = Runtime.getRuntime().totalMemory(); + final int freePercent = (int)(freeMemory * 100.0 / totalMemory + 0.5); + ramValues.add(100 - freePercent); + + ramGraph.setXLabel("Usage: " + String.format("%,d", (totalMemory - freeMemory) / MEGABYTE) + "mb (" + freePercent + "% free)"); + + // Trim the list + int k = ramValues.size(); + if ( k > 10 ) + ramValues.subList(0, k - 10).clear(); + + // Update the graph + ramGraph.setValues(ramValues); + }; + + // SwingUtilities.invokeLater is called so we don't run into threading issues with the GUI + executor.scheduleAtFixedRate(() -> SwingUtilities.invokeLater(periodicTask), 0, 1, TimeUnit.SECONDS); + } + + /** + * Make sure the JSplitPane divider is within a set of bounds + * + * @param splitPane The JSplitPane to check + */ + private void splitPaneLimit(JSplitPane splitPane) { + JRootPane frame = splitPane.getRootPane(); + int location = splitPane.getDividerLocation(); + if (location < frame.getWidth() - frame.getWidth() * 0.4f) { + splitPane.setDividerLocation(Math.round(frame.getWidth() - frame.getWidth() * 0.4f)); + } else if (location > frame.getWidth() - 200) { + splitPane.setDividerLocation(frame.getWidth() - 200); + } + } +} diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/GraphPanel.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/GraphPanel.java new file mode 100644 index 00000000000..eb259cf77ba --- /dev/null +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/GraphPanel.java @@ -0,0 +1,188 @@ +/* + * 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.standalone.gui; + +import lombok.Setter; + +import javax.swing.*; +import java.awt.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * This has been modified to fit Geyser more but is based on + * https://gist.github.com/roooodcastro/6325153#gistcomment-3107524 + */ +public final class GraphPanel extends JPanel { + private final static int padding = 10; + private final static int labelPadding = 25; + private final static int pointWidth = 4; + private final static int numberYDivisions = 10; + private final static Color lineColor = new Color(44, 102, 230, 180); + private final static Color pointColor = new Color(100, 100, 100, 180); + private final static Color gridColor = new Color(200, 200, 200, 200); + private static final Stroke graphStroke = new BasicStroke(2f); + private List values = new ArrayList<>(10); + + @Setter + private String xLabel = ""; + + public GraphPanel() { + setPreferredSize(new Dimension(200 - (padding * 2), 150 - (padding * 2))); + } + + public void setValues(Collection newValues) { + values.clear(); + addValues(newValues); + } + + public void addValues(Collection newValues) { + values.addAll(newValues); + updateUI(); + } + + @Override + protected void paintComponent(Graphics graphics) { + super.paintComponent(graphics); + if (!(graphics instanceof Graphics2D)) { + graphics.drawString("Graphics is not Graphics2D, unable to render", 0, 0); + return; + } + final Graphics2D g = (Graphics2D) graphics; + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + final int length = values.size(); + final int width = getWidth(); + final int height = getHeight(); + final int maxScore = getMaxScore(); + final int minScore = getMinScore(); + final int scoreRange = maxScore - minScore; + + // draw white background + g.setColor(Color.WHITE); + g.fillRect( + padding + labelPadding, + padding, + width - (2 * padding) - labelPadding, + height - 2 * padding - labelPadding); + g.setColor(Color.BLACK); + + final FontMetrics fontMetrics = g.getFontMetrics(); + final int fontHeight = fontMetrics.getHeight(); + + // create hatch marks and grid lines for y axis. + for (int i = 0; i < numberYDivisions + 1; i++) { + final int x1 = padding + labelPadding; + final int x2 = pointWidth + padding + labelPadding; + final int y = height - ((i * (height - padding * 2 - labelPadding)) / numberYDivisions + padding + labelPadding); + if (length > 0) { + g.setColor(gridColor); + g.drawLine(padding + labelPadding + 1 + pointWidth, y, width - padding, y); + + g.setColor(Color.BLACK); + final int tickValue = (int) (minScore + ((scoreRange * i) / numberYDivisions)); + final String yLabel = tickValue + ""; + final int labelWidth = fontMetrics.stringWidth(yLabel); + g.drawString(yLabel, x1 - labelWidth - 5, y + (fontHeight / 2) - 3); + } + g.drawLine(x1, y, x2, y); + } + + // and for x axis + if (length > 1) { + for (int i = 0; i < length; i++) { + final int x = i * (width - padding * 2 - labelPadding) / (length - 1) + padding + labelPadding; + final int y1 = height - padding - labelPadding; + final int y2 = y1 - pointWidth; + if ((i % ((int) ((length / 20.0)) + 1)) == 0) { + g.setColor(gridColor); + g.drawLine(x, height - padding - labelPadding - 1 - pointWidth, x, padding); + + g.setColor(Color.BLACK); + + /*g.setColor(Color.BLACK); + final String xLabel = i + ""; + final int labelWidth = fontMetrics.stringWidth(xLabel); + g.drawString(xLabel, x - labelWidth / 2, y1 + fontHeight + 3);*/ + } + g.drawLine(x, y1, x, y2); + } + } + + // create x and y axes + g.drawLine(padding + labelPadding, height - padding - labelPadding, padding + labelPadding, padding); + g.drawLine(padding + labelPadding, height - padding - labelPadding, width - padding, height - padding - labelPadding); + + g.setColor(Color.BLACK); + final int labelWidth = fontMetrics.stringWidth(xLabel); + final int labelX = ((padding + labelPadding) + (width - padding)) / 2; + final int labelY = height - padding - labelPadding; + g.drawString(xLabel, labelX - labelWidth / 2, labelY + fontHeight + 3); + + final Stroke oldStroke = g.getStroke(); + g.setColor(lineColor); + g.setStroke(graphStroke); + + final double xScale = ((double) width - (2 * padding) - labelPadding) / (length - 1); + final double yScale = ((double) height - 2 * padding - labelPadding) / scoreRange; + + final List graphPoints = new ArrayList<>(length); + for (int i = 0; i < length; i++) { + final int x1 = (int) (i * xScale + padding + labelPadding); + final int y1 = (int) ((maxScore - values.get(i)) * yScale + padding); + graphPoints.add(new Point(x1, y1)); + } + + for (int i = 0; i < graphPoints.size() - 1; i++) { + final int x1 = graphPoints.get(i).x; + final int y1 = graphPoints.get(i).y; + final int x2 = graphPoints.get(i + 1).x; + final int y2 = graphPoints.get(i + 1).y; + g.drawLine(x1, y1, x2, y2); + } + + boolean drawDots = width > (length * pointWidth); + if (drawDots) { + g.setStroke(oldStroke); + g.setColor(pointColor); + for (Point graphPoint : graphPoints) { + final int x = graphPoint.x - pointWidth / 2; + final int y = graphPoint.y - pointWidth / 2; + g.fillOval(x, y, pointWidth, pointWidth); + } + } + } + + private int getMinScore() { + return 0; + } + + private int getMaxScore() { + return 100; + } +} diff --git a/bootstrap/standalone/src/main/resources/icon.png b/bootstrap/standalone/src/main/resources/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..4e6a38a787f0ed02a93b2580b5be0e838ffefea3 GIT binary patch literal 115461 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�? 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 literal 0 HcmV?d00001 diff --git a/bootstrap/standalone/src/main/resources/log4j2.xml b/bootstrap/standalone/src/main/resources/log4j2.xml index 95741540525..238a27da5d8 100644 --- a/bootstrap/standalone/src/main/resources/log4j2.xml +++ b/bootstrap/standalone/src/main/resources/log4j2.xml @@ -1,9 +1,12 @@ - + + + + @@ -14,6 +17,7 @@ + diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 20a7712665b..6cde2a2db3a 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -44,17 +44,17 @@ import org.geysermc.connector.network.translators.BiomeTranslator; import org.geysermc.connector.network.translators.EntityIdentifierRegistry; import org.geysermc.connector.network.translators.PacketTranslatorRegistry; +import org.geysermc.connector.network.translators.effect.EffectRegistry; import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.network.translators.item.ItemTranslator; import org.geysermc.connector.network.translators.sound.SoundHandlerRegistry; +import org.geysermc.connector.network.translators.sound.SoundRegistry; import org.geysermc.connector.network.translators.world.WorldManager; import org.geysermc.connector.network.translators.world.block.BlockTranslator; -import org.geysermc.connector.network.translators.effect.EffectRegistry; import org.geysermc.connector.network.translators.world.block.entity.BlockEntityTranslator; import org.geysermc.connector.utils.DimensionUtils; import org.geysermc.connector.utils.DockerCheck; import org.geysermc.connector.utils.LocaleUtils; -import org.geysermc.connector.network.translators.sound.SoundRegistry; import java.net.InetSocketAddress; import java.text.DecimalFormat; @@ -158,8 +158,23 @@ private GeyserConnector(PlatformType platformType, GeyserBootstrap bootstrap) { metrics.addCustomChart(new Metrics.SimplePie("platform", platformType::getPlatformName)); } + boolean isGui = false; + // This will check if we are in standalone and get the 'useGui' variable from there + if (platformType == PlatformType.STANDALONE) { + try { + Class cls = Class.forName("org.geysermc.platform.standalone.GeyserStandaloneBootstrap"); + isGui = (boolean) cls.getMethod("isUseGui").invoke(cls.cast(bootstrap)); + } catch (Exception e) { e.printStackTrace(); } + } + double completeTime = (System.currentTimeMillis() - startupTime) / 1000D; - logger.info(String.format("Done (%ss)! Run /geyser help for help!", new DecimalFormat("#.###").format(completeTime))); + String message = String.format("Done (%ss)!", new DecimalFormat("#.###").format(completeTime)); + if (isGui) { + message += " Run Commands -> help for help!"; + } else { + message += " Run /geyser help for help!"; + } + logger.info(message); } public void shutdown() { From 4c74f82c1984906cdfebea2f11f310bef2d9d280 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Jul 2020 19:18:44 -0500 Subject: [PATCH 27/72] Bump log4j-core from 2.13.1 to 2.13.2 in /bootstrap/standalone (#886) Bumps log4j-core from 2.13.1 to 2.13.2. Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- bootstrap/standalone/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrap/standalone/pom.xml b/bootstrap/standalone/pom.xml index 770ca1009ed..df75f753325 100644 --- a/bootstrap/standalone/pom.xml +++ b/bootstrap/standalone/pom.xml @@ -63,7 +63,7 @@ org.apache.logging.log4j log4j-core - 2.13.1 + 2.13.2 org.apache.logging.log4j From 0daa4451ec08961b18296a9575737ce3ae4a0ea7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Jul 2020 19:18:44 -0500 Subject: [PATCH 28/72] Bump log4j-core from 2.13.1 to 2.13.2 in /bootstrap/standalone (#886) Bumps log4j-core from 2.13.1 to 2.13.2. Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- bootstrap/standalone/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrap/standalone/pom.xml b/bootstrap/standalone/pom.xml index 984a6baad22..60b0ba81f25 100644 --- a/bootstrap/standalone/pom.xml +++ b/bootstrap/standalone/pom.xml @@ -63,7 +63,7 @@ org.apache.logging.log4j log4j-core - 2.13.1 + 2.13.2 org.apache.logging.log4j From ab71bf0727ea425998056a4ae09c664b2f8e463a Mon Sep 17 00:00:00 2001 From: AJ Ferguson Date: Fri, 3 Jul 2020 12:18:35 -0800 Subject: [PATCH 29/72] Fix bug when dropping items from an open inventory --- .../inventory/action/InventoryActionDataTranslator.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/InventoryActionDataTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/InventoryActionDataTranslator.java index 426627bfb81..96cbd61fb08 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/InventoryActionDataTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/InventoryActionDataTranslator.java @@ -120,9 +120,9 @@ public static void translate(InventoryTranslator translator, GeyserSession sessi session.sendDownstreamPacket(dropPacket); } } - ItemStack item = session.getInventory().getItem(javaSlot); + ItemStack item = inventory.getItem(javaSlot); if (item != null) { - session.getInventory().setItem(javaSlot, new ItemStack(item.getId(), item.getAmount() - dropAmount, item.getNbt())); + inventory.setItem(javaSlot, new ItemStack(item.getId(), item.getAmount() - dropAmount, item.getNbt())); } return; } else { //clicking outside of inventory From da96a5b19c92fe6440bb066a5fba17fb6bbad46b Mon Sep 17 00:00:00 2001 From: rtm516 Date: Fri, 3 Jul 2020 22:55:54 +0100 Subject: [PATCH 30/72] Fix Strider cold state when riding and removing of the RIDING flag when a parent is killed --- .../org/geysermc/connector/entity/Entity.java | 6 ++++ .../entity/living/animal/StriderEntity.java | 34 +++++++++++++++++-- .../JavaEntitySetPassengersTranslator.java | 3 ++ 3 files changed, 41 insertions(+), 2 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 6ae9c67174d..8f242dcc651 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/Entity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/Entity.java @@ -146,6 +146,12 @@ public void spawnEntity(GeyserSession session) { public boolean despawnEntity(GeyserSession session) { if (!valid) return true; + for (long passenger : passengers) { + Entity entity = session.getEntityCache().getEntityByJavaId(passenger); + entity.getMetadata().getFlags().setFlag(EntityFlag.RIDING, false); + entity.updateBedrockMetadata(session); + } + RemoveEntityPacket removeEntityPacket = new RemoveEntityPacket(); removeEntityPacket.setUniqueEntityId(geyserId); session.sendUpstreamPacket(removeEntityPacket); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/StriderEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/StriderEntity.java index bb4daf545ff..ee6815d15a7 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/StriderEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/StriderEntity.java @@ -29,11 +29,14 @@ 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.Entity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; public class StriderEntity extends AnimalEntity { + private boolean shaking = false; + public StriderEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); @@ -44,8 +47,7 @@ public StriderEntity(long entityId, long geyserId, EntityType entityType, Vector @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { if (entityMetadata.getId() == 17) { - metadata.getFlags().setFlag(EntityFlag.BREATHING, !(boolean) entityMetadata.getValue()); - metadata.getFlags().setFlag(EntityFlag.SHAKING, (boolean) entityMetadata.getValue()); + shaking = (boolean) entityMetadata.getValue(); } if (entityMetadata.getId() == 18) { metadata.getFlags().setFlag(EntityFlag.SADDLED, (boolean) entityMetadata.getValue()); @@ -53,4 +55,32 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s super.updateBedrockMetadata(entityMetadata, session); } + + @Override + public void updateBedrockMetadata(GeyserSession session) { + // Make sure they are not shaking when riding another entity + // Needs to copy the parent state + if (metadata.getFlags().getFlag(EntityFlag.RIDING)) { + boolean parentShaking = false; + for (Entity ent : session.getEntityCache().getEntities().values()) { + if (ent.getPassengers().contains(entityId) && ent instanceof StriderEntity) { + parentShaking = ent.getMetadata().getFlags().getFlag(EntityFlag.SHAKING); + break; + } + } + + metadata.getFlags().setFlag(EntityFlag.BREATHING, !parentShaking); + metadata.getFlags().setFlag(EntityFlag.SHAKING, parentShaking); + } else { + metadata.getFlags().setFlag(EntityFlag.BREATHING, !shaking); + metadata.getFlags().setFlag(EntityFlag.SHAKING, shaking); + } + + // Update the passengers if we have any + for (long passenger : passengers) { + session.getEntityCache().getEntityByJavaId(passenger).updateBedrockMetadata(session); + } + + super.updateBedrockMetadata(session); + } } 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 87491fbf317..094d64df7ea 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 @@ -101,6 +101,9 @@ public void translate(ServerEntitySetPassengersPacket packet, GeyserSession sess this.updateOffset(passenger, entity.getEntityType(), session, false, false, (passengers.size() > 1)); } + + // Force an update to the passenger metadata + passenger.updateBedrockMetadata(session); } if (entity.getEntityType() == EntityType.HORSE) { From a7fbe995f826f427dfa589b535e255629519d24e Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Sat, 4 Jul 2020 10:26:32 -0400 Subject: [PATCH 31/72] Add comment and check for null when removing passengers --- .../src/main/java/org/geysermc/connector/entity/Entity.java | 5 +++-- 1 file changed, 3 insertions(+), 2 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 8f242dcc651..ffe13a50d72 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/Entity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/Entity.java @@ -146,9 +146,10 @@ public void spawnEntity(GeyserSession session) { public boolean despawnEntity(GeyserSession session) { if (!valid) return true; - for (long passenger : passengers) { + for (long passenger : passengers) { // Make sure all passengers on the despawned entity are updated Entity entity = session.getEntityCache().getEntityByJavaId(passenger); - entity.getMetadata().getFlags().setFlag(EntityFlag.RIDING, false); + if (entity == null) continue; + entity.getMetadata().getOrCreateFlags().setFlag(EntityFlag.RIDING, false); entity.updateBedrockMetadata(session); } From cc2bbc675f1db05878a3015eca7407ed015923cf Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sat, 4 Jul 2020 13:08:36 -0500 Subject: [PATCH 32/72] Update mappings submodule --- connector/src/main/resources/mappings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/src/main/resources/mappings b/connector/src/main/resources/mappings index b443f3d43ff..a298041e008 160000 --- a/connector/src/main/resources/mappings +++ b/connector/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit b443f3d43ff2da104816490e3190a3194ee610b2 +Subproject commit a298041e008d83e38b15248ebee5a38be2bc613f From 8ac5d6e13d92e3c8919a410c34d94850491a0861 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sat, 4 Jul 2020 16:35:48 -0500 Subject: [PATCH 33/72] Fix memory leak in legacy ping passthrough (Fixes #674, #813) --- .../GeyserBungeePingPassthrough.java | 9 ++- .../spigot/GeyserSpigotPingPassthrough.java | 12 +-- .../sponge/GeyserSpongePingPassthrough.java | 19 +++-- .../GeyserVelocityPingPassthrough.java | 16 ++-- .../connector/common/ping/GeyserPingInfo.java | 61 +++++++++++++-- .../network/ConnectorServerEventHandler.java | 8 +- .../connector/network/QueryPacketHandler.java | 8 +- .../ping/GeyserLegacyPingPassthrough.java | 74 ++++++++++++++----- 8 files changed, 153 insertions(+), 54 deletions(-) diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePingPassthrough.java b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePingPassthrough.java index ab400052092..f2166aad76b 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePingPassthrough.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePingPassthrough.java @@ -60,14 +60,15 @@ public GeyserPingInfo getPingInformation() { else future.complete(event); })); ProxyPingEvent event = future.join(); + ServerPing response = event.getResponse(); GeyserPingInfo geyserPingInfo = new GeyserPingInfo( - event.getResponse().getDescription(), - event.getResponse().getPlayers().getOnline(), - event.getResponse().getPlayers().getMax() + response.getDescriptionComponent().toLegacyText(), + new GeyserPingInfo.Players(response.getPlayers().getMax(), response.getPlayers().getOnline()), + new GeyserPingInfo.Version(response.getVersion().getName(), response.getVersion().getProtocol()) ); if (event.getResponse().getPlayers().getSample() != null) { Arrays.stream(event.getResponse().getPlayers().getSample()).forEach(proxiedPlayer -> { - geyserPingInfo.addPlayer(proxiedPlayer.getName()); + geyserPingInfo.getPlayerList().add(proxiedPlayer.getName()); }); } return geyserPingInfo; diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPingPassthrough.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPingPassthrough.java index 07999d87617..9658a68da28 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPingPassthrough.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPingPassthrough.java @@ -26,6 +26,7 @@ package org.geysermc.platform.spigot; +import com.github.steveice10.mc.protocol.MinecraftConstants; import lombok.AllArgsConstructor; import org.bukkit.Bukkit; import org.bukkit.entity.Player; @@ -48,14 +49,15 @@ public GeyserPingInfo getPingInformation() { try { ServerListPingEvent event = new GeyserPingEvent(InetAddress.getLocalHost(), Bukkit.getMotd(), Bukkit.getOnlinePlayers().size(), Bukkit.getMaxPlayers()); Bukkit.getPluginManager().callEvent(event); - GeyserPingInfo geyserPingInfo = new GeyserPingInfo(event.getMotd(), event.getNumPlayers(), event.getMaxPlayers()); - Bukkit.getOnlinePlayers().forEach(player -> { - geyserPingInfo.addPlayer(player.getName()); - }); + GeyserPingInfo geyserPingInfo = new GeyserPingInfo(event.getMotd(), + new GeyserPingInfo.Players(event.getMaxPlayers(), event.getNumPlayers()), + new GeyserPingInfo.Version(Bukkit.getVersion(), MinecraftConstants.PROTOCOL_VERSION) // thanks Spigot for not exposing this, just default to latest + ); + Bukkit.getOnlinePlayers().stream().map(Player::getName).forEach(geyserPingInfo.getPlayerList()::add); return geyserPingInfo; } catch (Exception e) { logger.debug("Error while getting Bukkit ping passthrough: " + e.toString()); - return new GeyserPingInfo(null, 0, 0); + return new GeyserPingInfo(null, null, null); } } diff --git a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePingPassthrough.java b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePingPassthrough.java index 99e8ed2f26b..862c744bb43 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePingPassthrough.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePingPassthrough.java @@ -26,6 +26,7 @@ package org.geysermc.platform.sponge; +import com.github.steveice10.mc.protocol.MinecraftConstants; import org.geysermc.connector.common.ping.GeyserPingInfo; import org.geysermc.connector.ping.IGeyserPingPassthrough; import org.spongepowered.api.MinecraftVersion; @@ -35,6 +36,7 @@ import org.spongepowered.api.event.cause.EventContext; import org.spongepowered.api.event.server.ClientPingServerEvent; import org.spongepowered.api.network.status.StatusClient; +import org.spongepowered.api.profile.GameProfile; import java.lang.reflect.Method; import java.net.Inet4Address; @@ -68,11 +70,18 @@ public GeyserPingInfo getPingInformation() { Sponge.getEventManager().post(event); GeyserPingInfo geyserPingInfo = new GeyserPingInfo( event.getResponse().getDescription().toPlain(), - event.getResponse().getPlayers().orElseThrow(IllegalStateException::new).getOnline(), - event.getResponse().getPlayers().orElseThrow(IllegalStateException::new).getMax()); - event.getResponse().getPlayers().get().getProfiles().forEach(player -> { - geyserPingInfo.addPlayer(player.getName().orElseThrow(IllegalStateException::new)); - }); + new GeyserPingInfo.Players( + event.getResponse().getPlayers().orElseThrow(IllegalStateException::new).getMax(), + event.getResponse().getPlayers().orElseThrow(IllegalStateException::new).getOnline() + ), + new GeyserPingInfo.Version( + event.getResponse().getVersion().getName(), + MinecraftConstants.PROTOCOL_VERSION) // thanks for also not exposing this sponge + ); + event.getResponse().getPlayers().get().getProfiles().stream() + .map(GameProfile::getName) + .map(op -> op.orElseThrow(IllegalStateException::new)) + .forEach(geyserPingInfo.getPlayerList()::add); return geyserPingInfo; } diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPingPassthrough.java b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPingPassthrough.java index 934c577405c..980c5b3ee19 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPingPassthrough.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPingPassthrough.java @@ -59,13 +59,17 @@ public GeyserPingInfo getPingInformation() { throw new RuntimeException(e); } GeyserPingInfo geyserPingInfo = new GeyserPingInfo( - LegacyComponentSerializer.INSTANCE.serialize(event.getPing().getDescription(), '§'), - event.getPing().getPlayers().orElseThrow(IllegalStateException::new).getOnline(), - event.getPing().getPlayers().orElseThrow(IllegalStateException::new).getMax() + LegacyComponentSerializer.legacy().serialize(event.getPing().getDescription(), '§'), + new GeyserPingInfo.Players( + event.getPing().getPlayers().orElseThrow(IllegalStateException::new).getMax(), + event.getPing().getPlayers().orElseThrow(IllegalStateException::new).getOnline() + ), + new GeyserPingInfo.Version( + event.getPing().getVersion().getName(), + event.getPing().getVersion().getProtocol() + ) ); - event.getPing().getPlayers().get().getSample().forEach(player -> { - geyserPingInfo.addPlayer(player.getName()); - }); + event.getPing().getPlayers().get().getSample().stream().map(ServerPing.SamplePlayer::getName).forEach(geyserPingInfo.getPlayerList()::add); return geyserPingInfo; } diff --git a/connector/src/main/java/org/geysermc/connector/common/ping/GeyserPingInfo.java b/connector/src/main/java/org/geysermc/connector/common/ping/GeyserPingInfo.java index 69b24ea1e1d..eff1fe49dca 100644 --- a/connector/src/main/java/org/geysermc/connector/common/ping/GeyserPingInfo.java +++ b/connector/src/main/java/org/geysermc/connector/common/ping/GeyserPingInfo.java @@ -26,8 +26,11 @@ package org.geysermc.connector.common.ping; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.databind.JsonNode; import lombok.Data; -import lombok.Getter; import java.util.ArrayList; import java.util.Collection; @@ -35,14 +38,56 @@ @Data public class GeyserPingInfo { - public final String motd; - public final int currentPlayerCount; - public final int maxPlayerCount; + private String description; - @Getter - private Collection players = new ArrayList<>(); + private Players players; + private Version version; - public void addPlayer(String username) { - players.add(username); + @JsonIgnore + private Collection playerList = new ArrayList<>(); + + public GeyserPingInfo() { + } + + public GeyserPingInfo(String description, Players players, Version version) { + this.description = description; + this.players = players; + this.version = version; + } + + @JsonSetter("description") + void setDescription(JsonNode description) { + this.description = description.toString(); + } + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Players { + + private int max; + private int online; + + public Players() { + } + + public Players(int max, int online) { + this.max = max; + this.online = online; + } + } + + @Data + public static class Version { + + private String name; + private int protocol; + + public Version() { + } + + public Version(String name, int protocol) { + this.name = name; + this.protocol = protocol; + } } } 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 11ff9a029c0..27b7ad8fe0b 100644 --- a/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java @@ -74,8 +74,8 @@ public BedrockPong onQuery(InetSocketAddress inetSocketAddress) { pong.setVersion(null); // Server tries to connect either way and it looks better pong.setIpv4Port(config.getBedrock().getPort()); - if (config.isPassthroughMotd() && pingInfo != null && pingInfo.motd != null) { - String[] motd = MessageUtils.getBedrockMessage(MessageSerializer.fromString(pingInfo.motd)).split("\n"); + if (config.isPassthroughMotd() && pingInfo != null && pingInfo.getDescription() != null) { + String[] motd = MessageUtils.getBedrockMessage(MessageSerializer.fromString(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. @@ -87,8 +87,8 @@ public BedrockPong onQuery(InetSocketAddress inetSocketAddress) { } if (config.isPassthroughPlayerCounts() && pingInfo != null) { - pong.setPlayerCount(pingInfo.currentPlayerCount); - pong.setMaximumPlayerCount(pingInfo.maxPlayerCount); + pong.setPlayerCount(pingInfo.getPlayers().getOnline()); + pong.setMaximumPlayerCount(pingInfo.getPlayers().getMax()); } else { pong.setPlayerCount(connector.getPlayers().size()); pong.setMaximumPlayerCount(config.getMaxPlayers()); 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 ba654c75beb..7b191263938 100644 --- a/connector/src/main/java/org/geysermc/connector/network/QueryPacketHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/QueryPacketHandler.java @@ -148,7 +148,7 @@ private byte[] getGameData() { } if (connector.getConfig().isPassthroughMotd() && pingInfo != null) { - String[] javaMotd = MessageUtils.getBedrockMessage(MessageSerializer.fromString(pingInfo.motd)).split("\n"); + String[] javaMotd = MessageUtils.getBedrockMessage(MessageSerializer.fromString(pingInfo.getDescription())).split("\n"); motd = javaMotd[0].trim(); // First line of the motd. } else { motd = connector.getConfig().getBedrock().getMotd1(); @@ -156,8 +156,8 @@ private byte[] getGameData() { // If passthrough player counts is enabled lets get players from the server if (connector.getConfig().isPassthroughPlayerCounts() && pingInfo != null) { - currentPlayerCount = String.valueOf(pingInfo.currentPlayerCount); - maxPlayerCount = String.valueOf(pingInfo.maxPlayerCount); + currentPlayerCount = String.valueOf(pingInfo.getPlayers().getOnline()); + maxPlayerCount = String.valueOf(pingInfo.getPlayers().getMax()); } else { currentPlayerCount = String.valueOf(connector.getPlayers().size()); maxPlayerCount = String.valueOf(connector.getConfig().getMaxPlayers()); @@ -220,7 +220,7 @@ private byte[] getPlayers() { // Fill player names if(pingInfo != null) { - for (String username : pingInfo.getPlayers()) { + for (String username : pingInfo.getPlayerList()) { query.write(username.getBytes()); query.write((byte) 0x00); } diff --git a/connector/src/main/java/org/geysermc/connector/ping/GeyserLegacyPingPassthrough.java b/connector/src/main/java/org/geysermc/connector/ping/GeyserLegacyPingPassthrough.java index 1c4d26d0b2e..aa9e0503db0 100644 --- a/connector/src/main/java/org/geysermc/connector/ping/GeyserLegacyPingPassthrough.java +++ b/connector/src/main/java/org/geysermc/connector/ping/GeyserLegacyPingPassthrough.java @@ -26,16 +26,21 @@ package org.geysermc.connector.ping; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.JsonMappingException; 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.message.TextMessage; -import com.github.steveice10.mc.protocol.data.status.handler.ServerInfoHandler; -import com.github.steveice10.packetlib.Client; -import com.github.steveice10.packetlib.tcp.TcpSessionFactory; +import com.nukkitx.nbt.util.VarInts; import org.geysermc.connector.common.ping.GeyserPingInfo; import org.geysermc.connector.GeyserConnector; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.ConnectException; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketTimeoutException; import java.util.concurrent.TimeUnit; public class GeyserLegacyPingPassthrough implements IGeyserPingPassthrough, Runnable { @@ -44,13 +49,10 @@ public class GeyserLegacyPingPassthrough implements IGeyserPingPassthrough, Runn public GeyserLegacyPingPassthrough(GeyserConnector connector) { this.connector = connector; - this.pingInfo = new GeyserPingInfo(null, 0, 0); } private GeyserPingInfo pingInfo; - private Client client; - /** * Start legacy ping passthrough thread * @param connector GeyserConnector @@ -76,15 +78,51 @@ public GeyserPingInfo getPingInformation() { @Override public void run() { try { - this.client = new Client(connector.getConfig().getRemote().getAddress(), connector.getConfig().getRemote().getPort(), new MinecraftProtocol(SubProtocol.STATUS), new TcpSessionFactory()); - this.client.getSession().setFlag(MinecraftConstants.SERVER_INFO_HANDLER_KEY, (ServerInfoHandler) (session, info) -> { - this.pingInfo = new GeyserPingInfo(((TextMessage) info.getDescription()).getText(), info.getPlayerInfo().getOnlinePlayers(), info.getPlayerInfo().getMaxPlayers()); - this.client.getSession().disconnect(null); - }); - - client.getSession().connect(); - } catch (Exception ex) { - ex.printStackTrace(); + Socket socket = new Socket(); + socket.connect(new InetSocketAddress(connector.getConfig().getRemote().getAddress(), connector.getConfig().getRemote().getPort()), 5000); + + ByteArrayOutputStream byteArrayStream = new ByteArrayOutputStream(); + DataOutputStream handshake = new DataOutputStream(byteArrayStream); + handshake.write(0x0); + VarInts.writeUnsignedInt(handshake, MinecraftConstants.PROTOCOL_VERSION); + VarInts.writeUnsignedInt(handshake, socket.getInetAddress().getHostAddress().length()); + handshake.writeBytes(socket.getInetAddress().getHostAddress()); + handshake.writeShort(socket.getPort()); + VarInts.writeUnsignedInt(handshake, 1); + + DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream()); + VarInts.writeUnsignedInt(dataOutputStream, byteArrayStream.size()); + dataOutputStream.write(byteArrayStream.toByteArray()); + dataOutputStream.writeByte(0x01); + dataOutputStream.writeByte(0x00); + + DataInputStream dataInputStream = new DataInputStream(socket.getInputStream()); + VarInts.readUnsignedInt(dataInputStream); + VarInts.readUnsignedInt(dataInputStream); + int length = VarInts.readUnsignedInt(dataInputStream); + byte[] buffer = new byte[length]; + dataInputStream.readFully(buffer); + dataOutputStream.writeByte(0x09); + dataOutputStream.writeByte(0x01); + dataOutputStream.writeLong(System.currentTimeMillis()); + + VarInts.readUnsignedInt(dataInputStream); + String json = new String(buffer); + + this.pingInfo = GeyserConnector.JSON_MAPPER.readValue(json, GeyserPingInfo.class); + + byteArrayStream.close(); + handshake.close(); + dataOutputStream.close(); + dataInputStream.close(); + socket.close(); + } catch (SocketTimeoutException | ConnectException ex) { + this.pingInfo = null; + this.connector.getLogger().debug("Connection timeout for ping passthrough."); + } catch (JsonParseException | JsonMappingException ex) { + this.connector.getLogger().error("Failed to parse json when pinging server!", ex); + } catch (IOException e) { + e.printStackTrace(); } } } From 4062f1ee550597bedf10232c0d9108f57b62404d Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Sun, 5 Jul 2020 00:03:51 -0400 Subject: [PATCH 34/72] Fix flower pots and item frames --- .../java/org/geysermc/connector/entity/ItemFrameEntity.java | 4 ++-- .../network/translators/world/block/BlockStateValues.java | 2 +- .../world/block/entity/FlowerPotBlockEntityTranslator.java | 5 +++-- 3 files changed, 6 insertions(+), 5 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 58edf29ddf5..bb923a313a9 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java @@ -150,7 +150,7 @@ public boolean despawnEntity(GeyserSession session) { updateBlockPacket.setBlockPosition(bedrockPosition); updateBlockPacket.setRuntimeId(0); updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.PRIORITY); - updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NO_GRAPHIC); //TODO: Used to be NONE + updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NETWORK); updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NEIGHBORS); session.sendUpstreamPacket(updateBlockPacket); session.getItemFrameCache().remove(position, entityId); @@ -178,7 +178,7 @@ public void updateBlock(GeyserSession session) { updateBlockPacket.setBlockPosition(bedrockPosition); updateBlockPacket.setRuntimeId(bedrockRuntimeId); updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.PRIORITY); - updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NO_GRAPHIC); //TODO Same + updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NETWORK); updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NEIGHBORS); session.sendUpstreamPacket(updateBlockPacket); 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 73957cad9bf..9dcfe485ac6 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 @@ -76,7 +76,7 @@ public static void storeBlockStateValues(Map.Entry entry, int return; } - if (entry.getKey().contains("potted_")) { + if (entry.getKey().contains("potted_") || entry.getKey().contains("flower_pot")) { FLOWER_POT_VALUES.put(javaBlockState, entry.getKey().replace("potted_", "")); return; } 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 691a85d3202..7bc199768ce 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 @@ -49,10 +49,11 @@ public void updateBlock(GeyserSession session, int blockState, Vector3i position updateBlockPacket.setDataLayer(0); updateBlockPacket.setRuntimeId(BlockTranslator.getBedrockBlockId(blockState)); updateBlockPacket.setBlockPosition(position); - updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.PRIORITY); - updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NO_GRAPHIC); //TODO: Check 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); } /** From 5958b5d0baacc7dfd6ced2a5d752e7703aef37dc Mon Sep 17 00:00:00 2001 From: rtm516 Date: Sun, 5 Jul 2020 21:07:49 +0100 Subject: [PATCH 35/72] Fix ram graph causing memory leak and add cleanup of options menu on reload --- .../platform/standalone/gui/GeyserStandaloneGUI.java | 1 + .../org/geysermc/platform/standalone/gui/GraphPanel.java | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) 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 cc40ae5374b..f4138354db9 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 @@ -254,6 +254,7 @@ public void write(byte[] b) { */ public void setupInterface(GeyserStandaloneLogger geyserStandaloneLogger, GeyserCommandManager geyserCommandManager) { commandsMenu.removeAll(); + optionsMenu.removeAll(); for (Map.Entry command : geyserCommandManager.getCommands().entrySet()) { // Remove the offhand command and any alias commands to prevent duplicates in the list diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/GraphPanel.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/GraphPanel.java index eb259cf77ba..0e336132790 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/GraphPanel.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/GraphPanel.java @@ -43,9 +43,9 @@ public final class GraphPanel extends JPanel { private final static int labelPadding = 25; private final static int pointWidth = 4; private final static int numberYDivisions = 10; - private final static Color lineColor = new Color(44, 102, 230, 180); - private final static Color pointColor = new Color(100, 100, 100, 180); - private final static Color gridColor = new Color(200, 200, 200, 200); + private final static Color lineColor = new Color(44, 102, 230, 255); + private final static Color pointColor = new Color(100, 100, 100, 255); + private final static Color gridColor = new Color(200, 200, 200, 255); private static final Stroke graphStroke = new BasicStroke(2f); private List values = new ArrayList<>(10); From da1674c8d671c50874ae73b989b82f81a79bf25b Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sun, 5 Jul 2020 15:58:43 -0500 Subject: [PATCH 36/72] Update to Cloudburst NBT 2.0 --- .../connector/entity/FireworkEntity.java | 29 +-- .../connector/entity/ItemFrameEntity.java | 70 +++---- .../network/session/GeyserSession.java | 4 +- .../network/translators/BiomeTranslator.java | 10 +- .../translators/EntityIdentifierRegistry.java | 8 +- .../BedrockBlockEntityDataTranslator.java | 80 ++++---- ...tionTrackingDBClientRequestTranslator.java | 34 ++-- .../inventory/AnvilInventoryTranslator.java | 3 +- .../DoubleChestInventoryTranslator.java | 34 ++-- .../holder/BlockInventoryHolder.java | 12 +- .../translators/item/ItemRegistry.java | 3 +- .../translators/item/ItemTranslator.java | 176 +++++++++--------- .../item/translators/BannerTranslator.java | 45 ++--- .../java/JavaDeclareRecipesTranslator.java | 4 +- .../java/world/JavaBlockValueTranslator.java | 26 +-- .../java/world/JavaChunkDataTranslator.java | 10 +- .../java/world/JavaExplosionTranslator.java | 2 +- .../java/world/JavaPlayEffectTranslator.java | 2 +- .../java/world/JavaTradeListTranslator.java | 97 +++++----- .../world/block/BlockStateValues.java | 6 +- .../world/block/BlockTranslator.java | 56 +++--- .../entity/BannerBlockEntityTranslator.java | 27 ++- .../entity/BedBlockEntityTranslator.java | 22 +-- .../block/entity/BedrockOnlyBlockEntity.java | 4 +- .../block/entity/BlockEntityTranslator.java | 32 ++-- .../entity/CampfireBlockEntityTranslator.java | 36 ++-- .../DoubleChestBlockEntityTranslator.java | 28 ++- .../entity/EmptyBlockEntityTranslator.java | 12 +- .../EndGatewayBlockEntityTranslator.java | 45 +++-- .../FlowerPotBlockEntityTranslator.java | 24 +-- .../entity/PistonBlockEntityTranslator.java | 24 +-- .../ShulkerBoxBlockEntityTranslator.java | 23 +-- .../entity/SignBlockEntityTranslator.java | 22 +-- .../entity/SkullBlockEntityTranslator.java | 28 ++- .../entity/SpawnerBlockEntityTranslator.java | 48 +++-- .../connector/utils/BlockEntityUtils.java | 5 +- .../geysermc/connector/utils/ChunkUtils.java | 18 +- .../connector/utils/InventoryUtils.java | 17 +- .../geysermc/connector/utils/ItemUtils.java | 6 - 39 files changed, 549 insertions(+), 583 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/entity/FireworkEntity.java b/connector/src/main/java/org/geysermc/connector/entity/FireworkEntity.java index e1a8e08af67..cf49dc5fad5 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/FireworkEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/FireworkEntity.java @@ -31,7 +31,9 @@ import com.github.steveice10.opennbt.tag.builtin.ListTag; import com.github.steveice10.opennbt.tag.builtin.Tag; import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.nbt.CompoundTagBuilder; +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtMapBuilder; +import com.nukkitx.nbt.NbtType; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.packet.SetEntityMotionPacket; import org.geysermc.connector.entity.type.EntityType; @@ -49,7 +51,6 @@ public FireworkEntity(long entityId, long geyserId, EntityType entityType, Vecto super(entityId, geyserId, entityType, position, motion, rotation); } - @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { if (entityMetadata.getId() == 7) { @@ -62,19 +63,19 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s CompoundTag fireworks = tag.get("Fireworks"); - CompoundTagBuilder fireworksBuilder = CompoundTagBuilder.builder(); + NbtMapBuilder fireworksBuilder = NbtMap.builder(); if (fireworks.get("Flight") != null) { - fireworksBuilder.byteTag("Flight", MathUtils.convertByte(fireworks.get("Flight").getValue())); + fireworksBuilder.putByte("Flight", MathUtils.convertByte(fireworks.get("Flight").getValue())); } - List explosions = new ArrayList<>(); + List explosions = new ArrayList<>(); if (fireworks.get("Explosions") != null) { for (Tag effect : ((ListTag) fireworks.get("Explosions")).getValue()) { CompoundTag effectData = (CompoundTag) effect; - CompoundTagBuilder effectBuilder = CompoundTagBuilder.builder(); + NbtMapBuilder effectBuilder = NbtMap.builder(); if (effectData.get("Type") != null) { - effectBuilder.byteTag("FireworkType", MathUtils.convertByte(effectData.get("Type").getValue())); + effectBuilder.putByte("FireworkType", MathUtils.convertByte(effectData.get("Type").getValue())); } if (effectData.get("Colors") != null) { @@ -86,7 +87,7 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s colors[i++] = FireworkColor.fromJavaID(color).getBedrockID(); } - effectBuilder.byteArrayTag("FireworkColor", colors); + effectBuilder.putByteArray("FireworkColor", colors); } if (effectData.get("FadeColors") != null) { @@ -98,24 +99,24 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s colors[i++] = FireworkColor.fromJavaID(color).getBedrockID(); } - effectBuilder.byteArrayTag("FireworkFade", colors); + effectBuilder.putByteArray("FireworkFade", colors); } if (effectData.get("Trail") != null) { - effectBuilder.byteTag("FireworkTrail", MathUtils.convertByte(effectData.get("Trail").getValue())); + effectBuilder.putByte("FireworkTrail", MathUtils.convertByte(effectData.get("Trail").getValue())); } if (effectData.get("Flicker") != null) { - effectBuilder.byteTag("FireworkFlicker", MathUtils.convertByte(effectData.get("Flicker").getValue())); + effectBuilder.putByte("FireworkFlicker", MathUtils.convertByte(effectData.get("Flicker").getValue())); } - explosions.add(effectBuilder.buildRootTag()); + explosions.add(effectBuilder.build()); } } - fireworksBuilder.tag(new com.nukkitx.nbt.tag.ListTag<>("Explosions", com.nukkitx.nbt.tag.CompoundTag.class, explosions)); + fireworksBuilder.putList("Explosions", NbtType.COMPOUND, explosions); - metadata.put(EntityData.DISPLAY_ITEM, CompoundTagBuilder.builder().tag(fireworksBuilder.build("Fireworks")).buildRootTag()); + metadata.put(EntityData.DISPLAY_ITEM, NbtMap.builder().put("Fireworks", fireworksBuilder.build())); } else if (entityMetadata.getId() == 8 && !entityMetadata.getValue().equals(OptionalInt.empty()) && ((OptionalInt) entityMetadata.getValue()).getAsInt() == session.getPlayerEntity().getEntityId()) { //Checks if the firework has an entity ID (used when a player is gliding) and checks to make sure the player that is gliding is the one getting sent the packet or else every player near the gliding player will boost too. PlayerEntity entity = session.getPlayerEntity(); 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 bb923a313a9..392cec24c27 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java @@ -30,8 +30,8 @@ import com.github.steveice10.mc.protocol.data.game.entity.object.HangingDirection; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3i; -import com.nukkitx.nbt.CompoundTagBuilder; -import com.nukkitx.nbt.tag.CompoundTag; +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket; import com.nukkitx.protocol.bedrock.packet.StartGamePacket; @@ -66,21 +66,21 @@ public class ItemFrameEntity extends Entity { /** * Cached item frame's Bedrock compound tag. */ - private CompoundTag cachedTag; + private NbtMap cachedTag; public ItemFrameEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation, HangingDirection direction) { super(entityId, geyserId, entityType, position, motion, rotation); - CompoundTagBuilder builder = CompoundTag.builder(); - builder.tag(CompoundTag.builder() - .stringTag("name", "minecraft:frame") - .intTag("version", BlockTranslator.getBlockStateVersion()) - .tag(CompoundTag.builder() - .intTag("facing_direction", direction.ordinal()) - .byteTag("item_frame_map_bit", (byte) 0) - .build("states")) - .build("block")); - builder.shortTag("id", (short) 199); - bedrockRuntimeId = BlockTranslator.getItemFrame(builder.buildRootTag()); + NbtMapBuilder builder = NbtMap.builder(); + NbtMapBuilder blockBuilder = NbtMap.builder() + .putString("name", "minecraft:frame") + .putInt("version", BlockTranslator.getBlockStateVersion()); + blockBuilder.put("states", NbtMap.builder() + .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()); bedrockPosition = Vector3i.from(position.getFloorX(), position.getFloorY(), position.getFloorZ()); } @@ -100,7 +100,7 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s if (entityMetadata.getId() == 7 && entityMetadata.getValue() != null) { ItemData itemData = ItemTranslator.translateToBedrock(session, (ItemStack) entityMetadata.getValue()); ItemEntry itemEntry = ItemRegistry.getItem((ItemStack) entityMetadata.getValue()); - CompoundTagBuilder builder = CompoundTag.builder(); + NbtMapBuilder builder = NbtMap.builder(); String blockName = ""; for (StartGamePacket.ItemEntry startGamePacketItemEntry : ItemRegistry.ITEMS) { @@ -110,17 +110,17 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s } } - builder.byteTag("Count", (byte) itemData.getCount()); + builder.putByte("Count", (byte) itemData.getCount()); if (itemData.getTag() != null) { - builder.tag(itemData.getTag().toBuilder().build("tag")); + builder.put("tag", itemData.getTag().toBuilder().build()); } - builder.shortTag("Damage", itemData.getDamage()); - builder.stringTag("Name", blockName); - CompoundTagBuilder tag = getDefaultTag().toBuilder(); - tag.tag(builder.build("Item")); - tag.floatTag("ItemDropChance", 1.0f); - tag.floatTag("ItemRotation", rotation); - cachedTag = tag.buildRootTag(); + builder.putShort("Damage", itemData.getDamage()); + builder.putString("Name", blockName); + NbtMapBuilder tag = getDefaultTag().toBuilder(); + tag.put("Item", builder.build()); + tag.putFloat("ItemDropChance", 1.0f); + tag.putFloat("ItemRotation", rotation); + cachedTag = tag.build(); updateBlock(session); } else if (entityMetadata.getId() == 7 && entityMetadata.getValue() == null && cachedTag != null) { @@ -133,9 +133,9 @@ else if (entityMetadata.getId() == 8) { updateBlock(session); return; } - CompoundTagBuilder builder = cachedTag.toBuilder(); - builder.floatTag("ItemRotation", rotation); - cachedTag = builder.buildRootTag(); + NbtMapBuilder builder = cachedTag.toBuilder(); + builder.putFloat("ItemRotation", rotation); + cachedTag = builder.build(); updateBlock(session); } else { @@ -158,14 +158,14 @@ public boolean despawnEntity(GeyserSession session) { return true; } - private CompoundTag getDefaultTag() { - CompoundTagBuilder builder = CompoundTag.builder(); - builder.intTag("x", bedrockPosition.getX()); - builder.intTag("y", bedrockPosition.getY()); - builder.intTag("z", bedrockPosition.getZ()); - builder.byteTag("isMovable", (byte) 1); - builder.stringTag("id", "ItemFrame"); - return builder.buildRootTag(); + private NbtMap getDefaultTag() { + NbtMapBuilder builder = NbtMap.builder(); + builder.putInt("x", bedrockPosition.getX()); + builder.putInt("y", bedrockPosition.getY()); + builder.putInt("z", bedrockPosition.getZ()); + builder.putByte("isMovable", (byte) 1); + builder.putString("id", "ItemFrame"); + return builder.build(); } /** 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 1fe5dec8bda..93c619bfca3 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 @@ -218,11 +218,11 @@ public void connect(RemoteServer remoteServer) { ChunkUtils.sendEmptyChunks(this, playerEntity.getPosition().toInt(), 0, false); BiomeDefinitionListPacket biomeDefinitionListPacket = new BiomeDefinitionListPacket(); - biomeDefinitionListPacket.setTag(BiomeTranslator.BIOMES); + biomeDefinitionListPacket.setDefinitions(BiomeTranslator.BIOMES); upstream.sendPacket(biomeDefinitionListPacket); AvailableEntityIdentifiersPacket entityPacket = new AvailableEntityIdentifiersPacket(); - entityPacket.setTag(EntityIdentifierRegistry.ENTITY_IDENTIFIERS); + entityPacket.setIdentifiers(EntityIdentifierRegistry.ENTITY_IDENTIFIERS); upstream.sendPacket(entityPacket); CreativeContentPacket creativePacket = new CreativeContentPacket(); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/BiomeTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/BiomeTranslator.java index 880c1c9cd32..23a36641eb1 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/BiomeTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/BiomeTranslator.java @@ -26,9 +26,9 @@ package org.geysermc.connector.network.translators; +import com.nukkitx.nbt.NBTInputStream; +import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtUtils; -import com.nukkitx.nbt.stream.NBTInputStream; -import com.nukkitx.nbt.tag.CompoundTag; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.utils.FileUtils; @@ -40,7 +40,7 @@ // Array index formula by https://wiki.vg/Chunk_Format public class BiomeTranslator { - public static final CompoundTag BIOMES; + public static final NbtMap BIOMES; private BiomeTranslator() { } @@ -53,10 +53,10 @@ public static void init() { /* Load biomes */ InputStream stream = FileUtils.getResource("bedrock/biome_definitions.dat"); - CompoundTag biomesTag; + NbtMap biomesTag; try (NBTInputStream biomenbtInputStream = NbtUtils.createNetworkReader(stream)) { - biomesTag = (CompoundTag) biomenbtInputStream.readTag(); + biomesTag = (NbtMap) biomenbtInputStream.readTag(); BIOMES = biomesTag; } catch (Exception ex) { GeyserConnector.getInstance().getLogger().warning("Failed to get biomes from biome definitions, is there something wrong with the file?"); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/EntityIdentifierRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/EntityIdentifierRegistry.java index cc9b2cd89d9..59f4ae8ba1d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/EntityIdentifierRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/EntityIdentifierRegistry.java @@ -26,9 +26,9 @@ package org.geysermc.connector.network.translators; +import com.nukkitx.nbt.NBTInputStream; +import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtUtils; -import com.nukkitx.nbt.stream.NBTInputStream; -import com.nukkitx.nbt.tag.CompoundTag; import org.geysermc.connector.utils.FileUtils; import java.io.InputStream; @@ -38,7 +38,7 @@ */ public class EntityIdentifierRegistry { - public static CompoundTag ENTITY_IDENTIFIERS; + public static NbtMap ENTITY_IDENTIFIERS; private EntityIdentifierRegistry() { } @@ -52,7 +52,7 @@ public static void init() { InputStream stream = FileUtils.getResource("bedrock/entity_identifiers.dat"); try (NBTInputStream nbtInputStream = NbtUtils.createNetworkReader(stream)) { - ENTITY_IDENTIFIERS = (CompoundTag) nbtInputStream.readTag(); + ENTITY_IDENTIFIERS = (NbtMap) nbtInputStream.readTag(); } catch (Exception e) { throw new AssertionError("Unable to get entities from entity identifiers", e); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockEntityDataTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockEntityDataTranslator.java index 9fe62bb4426..38b9403998a 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockEntityDataTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockEntityDataTranslator.java @@ -27,7 +27,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.github.steveice10.mc.protocol.packet.ingame.client.world.ClientUpdateSignPacket; -import com.nukkitx.nbt.tag.CompoundTag; +import com.nukkitx.nbt.NbtMap; import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; @@ -45,47 +45,45 @@ public class BedrockBlockEntityDataTranslator extends PacketTranslator lines.length - 1) { - break; - } - newMessage = new StringBuilder(); - } else newMessage.append(character); - } - // Put the final line on since it isn't done in the for loop - if (iterator < lines.length) lines[iterator] = newMessage.toString(); - ClientUpdateSignPacket clientUpdateSignPacket = new ClientUpdateSignPacket(pos, lines); - session.sendDownstreamPacket(clientUpdateSignPacket); - //TODO (potentially): originally I was going to update the sign blocks so Bedrock and Java users would match visually - // However Java can still store a lot per-line and visuals are still messed up so that doesn't work - - // We remove the sign position from map to indicate there is no work-in-progress sign - lastMessages.remove(pos); + NbtMap tag = packet.getData(); + if (tag.getString("id").equals("Sign")) { + // This is the reason why this all works - Bedrock sends packets every time you update the sign, Java only wants the final packet + // But Bedrock sends one final packet when you're done editing the sign, which should be equal to the last message since there's no edits + // So if the latest update does not match the last cached update then it's still being edited + Position pos = new Position(tag.getInt("x"), tag.getInt("y"), tag.getInt("z")); + if (!tag.getString("Text").equals(lastMessages.get(pos))) { + lastMessages.put(pos, tag.getString("Text")); + return; + } + // Otherwise the two messages are identical and we can get to work deconstructing + StringBuilder newMessage = new StringBuilder(); + // While Bedrock's sign lines are one string, Java's is an array of each line + // (Initialized all with empty strings because it complains about null) + String[] lines = new String[] {"", "", "", ""}; + int iterator = 0; + // This converts the message into the array'd message Java wants + for (char character : tag.getString("Text").toCharArray()) { + // If we get a return in Bedrock, that signals to use the next line. + if (character == '\n') { + lines[iterator] = newMessage.toString(); + iterator++; + // Bedrock, for whatever reason, can hold a message out of bounds + // We don't care about that so we discard that + if (iterator > lines.length - 1) { + break; + } + newMessage = new StringBuilder(); + } else newMessage.append(character); } + // Put the final line on since it isn't done in the for loop + if (iterator < lines.length) lines[iterator] = newMessage.toString(); + ClientUpdateSignPacket clientUpdateSignPacket = new ClientUpdateSignPacket(pos, lines); + session.sendDownstreamPacket(clientUpdateSignPacket); + //TODO (potentially): originally I was going to update the sign blocks so Bedrock and Java users would match visually + // However Java can still store a lot per-line and visuals are still messed up so that doesn't work + + // We remove the sign position from map to indicate there is no work-in-progress sign + lastMessages.remove(pos); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockPositionTrackingDBClientRequestTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockPositionTrackingDBClientRequestTranslator.java index 1ca467206e6..41b6246c9a8 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockPositionTrackingDBClientRequestTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockPositionTrackingDBClientRequestTranslator.java @@ -26,19 +26,19 @@ package org.geysermc.connector.network.translators.bedrock; -import com.nukkitx.nbt.CompoundTagBuilder; -import com.nukkitx.nbt.tag.IntTag; +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtMapBuilder; +import com.nukkitx.nbt.NbtType; import com.nukkitx.protocol.bedrock.packet.PositionTrackingDBClientRequestPacket; import com.nukkitx.protocol.bedrock.packet.PositionTrackingDBServerBroadcastPacket; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; 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.DimensionUtils; import org.geysermc.connector.utils.LoadstoneTracker; -import java.util.ArrayList; -import java.util.List; - @Translator(packet = PositionTrackingDBClientRequestPacket.class) public class BedrockPositionTrackingDBClientRequestTranslator extends PacketTranslator { @@ -60,22 +60,20 @@ public void translate(PositionTrackingDBClientRequestPacket packet, GeyserSessio broadcastPacket.setAction(PositionTrackingDBServerBroadcastPacket.Action.UPDATE); // Build the nbt data for the update - CompoundTagBuilder builder = CompoundTagBuilder.builder(); - builder.intTag("dim", DimensionUtils.javaToBedrock(pos.getDimension())); - builder.stringTag("id", String.format("%08X", packet.getTrackingId())); + NbtMapBuilder builder = NbtMap.builder(); + builder.putInt("dim", DimensionUtils.javaToBedrock(pos.getDimension())); + builder.putString("id", String.format("%08X", packet.getTrackingId())); - builder.byteTag("version", (byte) 1); // Not sure what this is for - builder.byteTag("status", (byte) 0); // Not sure what this is for + builder.putByte("version", (byte) 1); // Not sure what this is for + builder.putByte("status", (byte) 0); // Not sure what this is for // Build the position for the update - List posList = new ArrayList<>(); - posList.add(new IntTag("", pos.getX())); - posList.add(new IntTag("", pos.getY())); - posList.add(new IntTag("", pos.getZ())); - - builder.listTag("pos", IntTag.class, posList); - - broadcastPacket.setTag(builder.buildRootTag()); + IntList posList = new IntArrayList(); + posList.add(pos.getX()); + posList.add(pos.getY()); + posList.add(pos.getZ()); + builder.putList("pos", NbtType.INT, posList); + broadcastPacket.setTag(builder.build()); session.sendUpstreamPacket(broadcastPacket); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/AnvilInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/AnvilInventoryTranslator.java index c5d1de252d7..c780038a0c7 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/AnvilInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/AnvilInventoryTranslator.java @@ -30,6 +30,7 @@ import com.github.steveice10.mc.protocol.data.message.TextMessage; import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientRenameItemPacket; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.nukkitx.nbt.NbtMap; import com.nukkitx.protocol.bedrock.data.inventory.*; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; @@ -103,7 +104,7 @@ public void translateActions(GeyserSession session, Inventory inventory, List getAppliedItems(); - public CompoundTag translateNbtToBedrock(com.github.steveice10.opennbt.tag.builtin.CompoundTag tag) { - Map> javaValue = new HashMap<>(); + public NbtMap translateNbtToBedrock(com.github.steveice10.opennbt.tag.builtin.CompoundTag tag) { + Map javaValue = new HashMap<>(); if (tag.getValue() != null && !tag.getValue().isEmpty()) { for (String str : tag.getValue().keySet()) { com.github.steveice10.opennbt.tag.builtin.Tag javaTag = tag.get(str); - com.nukkitx.nbt.tag.Tag translatedTag = translateToBedrockNBT(javaTag); + Object translatedTag = translateToBedrockNBT(javaTag); if (translatedTag == null) continue; - javaValue.put(translatedTag.getName(), translatedTag); + javaValue.put(javaTag.getName(), translatedTag); } } - - return new CompoundTag(tag.getName(), javaValue); + NbtMapBuilder builder = NbtMap.builder(); + javaValue.forEach(builder::put); + return builder.build(); } - private Tag translateToBedrockNBT(com.github.steveice10.opennbt.tag.builtin.Tag tag) { + private Object translateToBedrockNBT(com.github.steveice10.opennbt.tag.builtin.Tag tag) { if (tag instanceof ByteArrayTag) { - ByteArrayTag byteArrayTag = (ByteArrayTag) tag; - return new com.nukkitx.nbt.tag.ByteArrayTag(byteArrayTag.getName(), byteArrayTag.getValue()); + return ((ByteArrayTag) tag).getValue(); } if (tag instanceof ByteTag) { - ByteTag byteTag = (ByteTag) tag; - return new com.nukkitx.nbt.tag.ByteTag(byteTag.getName(), byteTag.getValue()); + return ((ByteTag) tag).getValue(); } if (tag instanceof DoubleTag) { - DoubleTag doubleTag = (DoubleTag) tag; - return new com.nukkitx.nbt.tag.DoubleTag(doubleTag.getName(), doubleTag.getValue()); + return ((DoubleTag) tag).getValue(); } if (tag instanceof FloatTag) { - FloatTag floatTag = (FloatTag) tag; - return new com.nukkitx.nbt.tag.FloatTag(floatTag.getName(), floatTag.getValue()); + return ((FloatTag) tag).getValue(); } if (tag instanceof IntArrayTag) { - IntArrayTag intArrayTag = (IntArrayTag) tag; - return new com.nukkitx.nbt.tag.IntArrayTag(intArrayTag.getName(), intArrayTag.getValue()); + return ((IntArrayTag) tag).getValue(); } if (tag instanceof IntTag) { - IntTag intTag = (IntTag) tag; - return new com.nukkitx.nbt.tag.IntTag(intTag.getName(), intTag.getValue()); + return ((IntTag) tag).getValue(); } if (tag instanceof LongArrayTag) { @@ -260,50 +266,46 @@ private Tag translateToBedrockNBT(com.github.steveice10.opennbt.tag.builtin.T } if (tag instanceof LongTag) { - LongTag longTag = (LongTag) tag; - return new com.nukkitx.nbt.tag.LongTag(longTag.getName(), longTag.getValue()); + return ((LongTag) tag).getValue(); } if (tag instanceof ShortTag) { - ShortTag shortTag = (ShortTag) tag; - return new com.nukkitx.nbt.tag.ShortTag(shortTag.getName(), shortTag.getValue()); + return ((ShortTag) tag).getValue(); } if (tag instanceof StringTag) { - StringTag stringTag = (StringTag) tag; - return new com.nukkitx.nbt.tag.StringTag(stringTag.getName(), stringTag.getValue()); + return ((StringTag) tag).getValue(); } if (tag instanceof ListTag) { ListTag listTag = (ListTag) tag; - List> tagList = new ArrayList<>(); + List tagList = new ArrayList<>(); for (com.github.steveice10.opennbt.tag.builtin.Tag value : listTag) { tagList.add(translateToBedrockNBT(value)); } - Class clazz = CompoundTag.class; + NbtType type = NbtType.COMPOUND; if (!tagList.isEmpty()) { - clazz = tagList.get(0).getClass(); + type = NbtType.byClass(tagList.get(0).getClass()); } - return new com.nukkitx.nbt.tag.ListTag(listTag.getName(), clazz, tagList); + return new NbtList(type, tagList); } if (tag instanceof com.github.steveice10.opennbt.tag.builtin.CompoundTag) { com.github.steveice10.opennbt.tag.builtin.CompoundTag compoundTag = (com.github.steveice10.opennbt.tag.builtin.CompoundTag) tag; - return translateNbtToBedrock(compoundTag); } return null; } - public com.github.steveice10.opennbt.tag.builtin.CompoundTag translateToJavaNBT(com.nukkitx.nbt.tag.CompoundTag tag) { - com.github.steveice10.opennbt.tag.builtin.CompoundTag javaTag = new com.github.steveice10.opennbt.tag.builtin.CompoundTag(tag.getName()); + public com.github.steveice10.opennbt.tag.builtin.CompoundTag translateToJavaNBT(String name, NbtMap tag) { + com.github.steveice10.opennbt.tag.builtin.CompoundTag javaTag = new com.github.steveice10.opennbt.tag.builtin.CompoundTag(name); Map javaValue = javaTag.getValue(); - if (tag.getValue() != null && !tag.getValue().isEmpty()) { - for (String str : tag.getValue().keySet()) { - Tag bedrockTag = tag.get(str); - com.github.steveice10.opennbt.tag.builtin.Tag translatedTag = translateToJavaNBT(bedrockTag); + if (tag != null && !tag.isEmpty()) { + for (String str : tag.keySet()) { + Object bedrockTag = tag.get(str); + com.github.steveice10.opennbt.tag.builtin.Tag translatedTag = translateToJavaNBT(name, bedrockTag); if (translatedTag == null) continue; @@ -315,77 +317,65 @@ public com.github.steveice10.opennbt.tag.builtin.CompoundTag translateToJavaNBT( return javaTag; } - private com.github.steveice10.opennbt.tag.builtin.Tag translateToJavaNBT(com.nukkitx.nbt.tag.Tag tag) { - if (tag instanceof com.nukkitx.nbt.tag.ByteArrayTag) { - com.nukkitx.nbt.tag.ByteArrayTag byteArrayTag = (com.nukkitx.nbt.tag.ByteArrayTag) tag; - return new ByteArrayTag(byteArrayTag.getName(), byteArrayTag.getValue()); + private com.github.steveice10.opennbt.tag.builtin.Tag translateToJavaNBT(String name, Object object) { + if (object instanceof int[]) { + return new IntArrayTag(name, (int[]) object); } - if (tag instanceof com.nukkitx.nbt.tag.ByteTag) { - com.nukkitx.nbt.tag.ByteTag byteTag = (com.nukkitx.nbt.tag.ByteTag) tag; - return new ByteTag(byteTag.getName(), byteTag.getValue()); + if (object instanceof byte[]) { + return new ByteArrayTag(name, (byte[]) object); } - - if (tag instanceof com.nukkitx.nbt.tag.DoubleTag) { - com.nukkitx.nbt.tag.DoubleTag doubleTag = (com.nukkitx.nbt.tag.DoubleTag) tag; - return new DoubleTag(doubleTag.getName(), doubleTag.getValue()); + + if (object instanceof Byte) { + return new ByteTag(name, (byte) object); } - if (tag instanceof com.nukkitx.nbt.tag.FloatTag) { - com.nukkitx.nbt.tag.FloatTag floatTag = (com.nukkitx.nbt.tag.FloatTag) tag; - return new FloatTag(floatTag.getName(), floatTag.getValue()); + if (object instanceof Float) { + return new FloatTag(name, (float) object); } - if (tag instanceof com.nukkitx.nbt.tag.IntArrayTag) { - com.nukkitx.nbt.tag.IntArrayTag intArrayTag = (com.nukkitx.nbt.tag.IntArrayTag) tag; - return new IntArrayTag(intArrayTag.getName(), intArrayTag.getValue()); + if (object instanceof Double) { + return new DoubleTag(name, (double) object); } - if (tag instanceof com.nukkitx.nbt.tag.IntTag) { - com.nukkitx.nbt.tag.IntTag intTag = (com.nukkitx.nbt.tag.IntTag) tag; - return new IntTag(intTag.getName(), intTag.getValue()); + if (object instanceof Integer) { + return new IntTag(name, (int) object); } - if (tag instanceof com.nukkitx.nbt.tag.LongArrayTag) { - com.nukkitx.nbt.tag.LongArrayTag longArrayTag = (com.nukkitx.nbt.tag.LongArrayTag) tag; - return new LongArrayTag(longArrayTag.getName(), longArrayTag.getValue()); + if (object instanceof long[]) { + return new LongArrayTag(name, (long[]) object); } - if (tag instanceof com.nukkitx.nbt.tag.LongTag) { - com.nukkitx.nbt.tag.LongTag longTag = (com.nukkitx.nbt.tag.LongTag) tag; - return new LongTag(longTag.getName(), longTag.getValue()); + if (object instanceof Long) { + return new LongTag(name, (long) object); } - if (tag instanceof com.nukkitx.nbt.tag.ShortTag) { - com.nukkitx.nbt.tag.ShortTag shortTag = (com.nukkitx.nbt.tag.ShortTag) tag; - return new ShortTag(shortTag.getName(), shortTag.getValue()); + if (object instanceof Short) { + return new ShortTag(name, (short) object); } - if (tag instanceof com.nukkitx.nbt.tag.StringTag) { - com.nukkitx.nbt.tag.StringTag stringTag = (com.nukkitx.nbt.tag.StringTag) tag; - return new StringTag(stringTag.getName(), stringTag.getValue()); + if (object instanceof String) { + return new StringTag(name, (String) object); } - if (tag instanceof com.nukkitx.nbt.tag.ListTag) { - com.nukkitx.nbt.tag.ListTag listTag = (com.nukkitx.nbt.tag.ListTag) tag; - + if (object instanceof List) { List tags = new ArrayList<>(); - for (Object value : listTag.getValue()) { - if (!(value instanceof com.nukkitx.nbt.tag.Tag)) - continue; - - com.nukkitx.nbt.tag.Tag tagValue = (com.nukkitx.nbt.tag.Tag) value; - com.github.steveice10.opennbt.tag.builtin.Tag javaTag = translateToJavaNBT(tagValue); + for (Object value : (List) object) { + com.github.steveice10.opennbt.tag.builtin.Tag javaTag = translateToJavaNBT("", value); if (javaTag != null) tags.add(javaTag); } - return new ListTag(listTag.getName(), tags); + return new ListTag(name, tags); } - if (tag instanceof com.nukkitx.nbt.tag.CompoundTag) { - com.nukkitx.nbt.tag.CompoundTag compoundTag = (com.nukkitx.nbt.tag.CompoundTag) tag; - return translateToJavaNBT(compoundTag); + if (object instanceof NbtMap) { + NbtMap map = (NbtMap) object; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue().equals(map.get(name))) { + return translateToJavaNBT(entry.getKey(), map.getCompound(name)); + } + } } return null; 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 8a1a973b012..304ea3fb2c3 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 @@ -31,7 +31,10 @@ 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.nukkitx.nbt.CompoundTagBuilder; +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.ItemRegistry; @@ -63,10 +66,10 @@ public ItemData translateToBedrock(ItemStack itemStack, ItemEntry itemEntry) { if (blockEntityTag.contains("Patterns")) { ListTag patterns = blockEntityTag.get("Patterns"); - CompoundTagBuilder builder = itemData.getTag().toBuilder(); - builder.tag(convertBannerPattern(patterns)); + NbtMapBuilder builder = itemData.getTag().toBuilder(); + builder.put("Patterns", convertBannerPattern(patterns)); - itemData = ItemData.of(itemData.getId(), itemData.getDamage(), itemData.getCount(), builder.buildRootTag()); + itemData = ItemData.of(itemData.getId(), itemData.getDamage(), itemData.getCount(), builder.build()); } return itemData; @@ -78,9 +81,9 @@ public ItemStack translateToJava(ItemData itemData, ItemEntry itemEntry) { ItemStack itemStack = super.translateToJava(itemData, itemEntry); - com.nukkitx.nbt.tag.CompoundTag nbtTag = itemData.getTag(); - if (nbtTag.contains("Patterns")) { - com.nukkitx.nbt.tag.ListTag patterns = nbtTag.get("Patterns"); + 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)); @@ -102,16 +105,16 @@ public List getAppliedItems() { * @param patterns The patterns to convert * @return The new converted patterns */ - public static com.nukkitx.nbt.tag.ListTag convertBannerPattern(ListTag patterns) { - List tagsList = new ArrayList<>(); + public static NbtList convertBannerPattern(ListTag patterns) { + List tagsList = new ArrayList<>(); for (com.github.steveice10.opennbt.tag.builtin.Tag patternTag : patterns.getValue()) { - com.nukkitx.nbt.tag.CompoundTag newPatternTag = getBedrockBannerPattern((CompoundTag) patternTag); + NbtMap newPatternTag = getBedrockBannerPattern((CompoundTag) patternTag); if (newPatternTag != null) { tagsList.add(newPatternTag); } } - return new com.nukkitx.nbt.tag.ListTag<>("Patterns", com.nukkitx.nbt.tag.CompoundTag.class, tagsList); + return new NbtList<>(NbtType.COMPOUND, tagsList); } /** @@ -120,7 +123,7 @@ public static com.nukkitx.nbt.tag.ListTag convertBannerPattern(ListTag patterns) * @param pattern Java edition pattern nbt * @return The Bedrock edition format pattern nbt */ - public static com.nukkitx.nbt.tag.CompoundTag getBedrockBannerPattern(CompoundTag pattern) { + public static NbtMap getBedrockBannerPattern(CompoundTag pattern) { String patternName = (String) pattern.get("Pattern").getValue(); // Return null if its the globe pattern as it doesn't exist on bedrock @@ -128,11 +131,11 @@ public static com.nukkitx.nbt.tag.CompoundTag getBedrockBannerPattern(CompoundTa return null; } - return CompoundTagBuilder.builder() - .intTag("Color", 15 - (int) pattern.get("Color").getValue()) - .stringTag("Pattern", (String) pattern.get("Pattern").getValue()) - .stringTag("Pattern", patternName) - .buildRootTag(); + return NbtMap.builder() + .putInt("Color", 15 - (int) pattern.get("Color").getValue()) + .putString("Pattern", (String) pattern.get("Pattern").getValue()) + .putString("Pattern", patternName) + .build(); } /** @@ -141,10 +144,10 @@ public static com.nukkitx.nbt.tag.CompoundTag getBedrockBannerPattern(CompoundTa * @param patterns The patterns to convert * @return The new converted patterns */ - public static ListTag convertBannerPattern(com.nukkitx.nbt.tag.ListTag patterns) { + public static ListTag convertBannerPattern(List patterns) { List tagsList = new ArrayList<>(); - for (Object patternTag : patterns.getValue()) { - CompoundTag newPatternTag = getJavaBannerPattern((com.nukkitx.nbt.tag.CompoundTag) patternTag); + for (Object patternTag : patterns) { + CompoundTag newPatternTag = getJavaBannerPattern((NbtMap) patternTag); tagsList.add(newPatternTag); } @@ -157,7 +160,7 @@ public static ListTag convertBannerPattern(com.nukkitx.nbt.tag.ListTag patter * @param pattern Bedorck edition pattern nbt * @return The Java edition format pattern nbt */ - public static CompoundTag getJavaBannerPattern(com.nukkitx.nbt.tag.CompoundTag pattern) { + public static CompoundTag getJavaBannerPattern(NbtMap pattern) { Map tags = new HashMap<>(); tags.put("Color", new IntTag("Color", 15 - pattern.getInt("Color"))); tags.put("Pattern", new StringTag("Pattern", pattern.getString("Pattern"))); 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 15f0e496e7b..08022640f2c 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 @@ -30,7 +30,7 @@ import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapedRecipeData; import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapelessRecipeData; import com.github.steveice10.mc.protocol.packet.ingame.server.ServerDeclareRecipesPacket; -import com.nukkitx.nbt.tag.CompoundTag; +import com.nukkitx.nbt.NbtMap; import com.nukkitx.protocol.bedrock.data.inventory.CraftingData; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.data.inventory.PotionMixData; @@ -169,6 +169,6 @@ private ItemData[][] combinations(GeyserSession session, Ingredient[] ingredient private static class GroupedItem { int id; int count; - CompoundTag tag; + NbtMap tag; } } 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 b304ed77fbe..3d3df51c91e 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 @@ -28,8 +28,8 @@ 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; -import com.nukkitx.nbt.CompoundTagBuilder; -import com.nukkitx.nbt.tag.CompoundTag; +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket; import com.nukkitx.protocol.bedrock.packet.BlockEventPacket; import org.geysermc.connector.network.session.GeyserSession; @@ -132,16 +132,16 @@ private void retractPiston(GeyserSession session, Vector3i position, float progr * @param state * @return Bedrock CompoundTag of piston */ - private CompoundTag buildPistonTag(Vector3i position, float progress, float lastProgress, byte state) { - CompoundTagBuilder builder = CompoundTag.EMPTY.toBuilder(); - builder.intTag("x", position.getX()) - .intTag("y", position.getY()) - .intTag("z", position.getZ()) - .floatTag("Progress", progress) - .floatTag("LastProgress", lastProgress) - .stringTag("id", "PistonArm") - .byteTag("NewState", state) - .byteTag("State", state); - return builder.buildRootTag(); + private NbtMap buildPistonTag(Vector3i position, float progress, float lastProgress, byte state) { + NbtMapBuilder builder = NbtMap.builder() + .putInt("x", position.getX()) + .putInt("y", position.getY()) + .putInt("z", position.getZ()) + .putFloat("Progress", progress) + .putFloat("LastProgress", lastProgress) + .putString("id", "PistonArm") + .putByte("NewState", state) + .putByte("State", state); + return builder.build(); } } 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 8b61bc1a576..556d0eab331 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 @@ -27,9 +27,9 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerChunkDataPacket; +import com.nukkitx.nbt.NBTOutputStream; +import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtUtils; -import com.nukkitx.nbt.stream.NBTOutputStream; -import com.nukkitx.nbt.tag.CompoundTag; import com.nukkitx.network.VarInts; import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket; @@ -82,8 +82,8 @@ public void translate(ServerChunkDataPacket packet, GeyserSession session) { ByteBufOutputStream stream = new ByteBufOutputStream(Unpooled.buffer()); NBTOutputStream nbtStream = NbtUtils.createNetworkWriter(stream); - for (CompoundTag blockEntity : chunkData.getBlockEntities()) { - nbtStream.write(blockEntity); + for (NbtMap blockEntity : chunkData.getBlockEntities()) { + nbtStream.writeTag(blockEntity); } byteBuf.writeBytes(stream.buffer()); @@ -100,7 +100,7 @@ public void translate(ServerChunkDataPacket packet, GeyserSession session) { session.sendUpstreamPacket(levelChunkPacket); // Some block entities need to be loaded in later or else text doesn't show (signs) or they crash the game (end gateway blocks) - for (Object2IntMap.Entry blockEntityEntry : chunkData.getLoadBlockEntitiesLater().object2IntEntrySet()) { + for (Object2IntMap.Entry blockEntityEntry : chunkData.getLoadBlockEntitiesLater().object2IntEntrySet()) { int x = blockEntityEntry.getKey().getInt("x"); int y = blockEntityEntry.getKey().getInt("y"); int z = blockEntityEntry.getKey().getInt("z"); 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 79b27f6a5f2..23e831c026e 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 @@ -52,7 +52,7 @@ public void translate(ServerExplosionPacket packet, GeyserSession session) { Vector3f pos = Vector3f.from(packet.getX(), packet.getY(), packet.getZ()); // Since bedrock does not play an explosion sound and particles sound, we have to manually do so LevelEventPacket levelEventPacket = new LevelEventPacket(); - levelEventPacket.setType(packet.getRadius() >= 2.0f ? LevelEventType.PARTICLE_HUGE_EXPLODE : LevelEventType.PARTICLE_LARGE_EXPLOSION); + levelEventPacket.setType(packet.getRadius() >= 2.0f ? LevelEventType.PARTICLE_HUGE_EXPLODE : LevelEventType.PARTICLE_EXPLOSION); levelEventPacket.setData(0); levelEventPacket.setPosition(pos.toFloat()); session.sendUpstreamPacket(levelEventPacket); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaPlayEffectTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaPlayEffectTranslator.java index 70c2749ddec..83d6bb69fae 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaPlayEffectTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaPlayEffectTranslator.java @@ -75,7 +75,7 @@ public void translate(ServerPlayEffectPacket packet, GeyserSession session) { effect.setData(BlockTranslator.getBedrockBlockId(breakBlockEffectData.getBlockState())); break; case EXPLOSION: - effect.setType(LevelEventType.PARTICLE_LARGE_EXPLOSION); + effect.setType(LevelEventType.PARTICLE_EXPLOSION); break; case MOB_SPAWN: effect.setType(LevelEventType.PARTICLE_MOB_BLOCK_SPAWN); // TODO: Check, but I don't think I really verified this ever went into effect on Java 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 d9d23f32351..10acb8fd894 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 @@ -29,8 +29,9 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.github.steveice10.mc.protocol.data.game.window.VillagerTrade; import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerTradeListPacket; -import com.nukkitx.nbt.CompoundTagBuilder; -import com.nukkitx.nbt.tag.CompoundTag; +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtMapBuilder; +import com.nukkitx.nbt.NbtType; import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; @@ -75,67 +76,67 @@ public void translate(ServerTradeListPacket packet, GeyserSession session) { updateTradePacket.setUsingEconomyTrade(true); updateTradePacket.setPlayerUniqueEntityId(session.getPlayerEntity().getGeyserId()); updateTradePacket.setTraderUniqueEntityId(session.getPlayerEntity().getGeyserId()); - CompoundTagBuilder builder = CompoundTagBuilder.builder(); - List tags = new ArrayList<>(); + NbtMapBuilder builder = NbtMap.builder(); + List tags = new ArrayList<>(); for (VillagerTrade trade : packet.getTrades()) { - CompoundTagBuilder recipe = CompoundTagBuilder.builder(); - recipe.intTag("maxUses", trade.getMaxUses()); - recipe.intTag("traderExp", trade.getXp()); - recipe.floatTag("priceMultiplierA", trade.getPriceMultiplier()); - recipe.tag(getItemTag(session, trade.getOutput(), "sell", 0)); - recipe.floatTag("priceMultiplierB", 0.0f); - recipe.intTag("buyCountB", trade.getSecondInput() != null ? trade.getSecondInput().getAmount() : 0); - recipe.intTag("buyCountA", trade.getFirstInput().getAmount()); - recipe.intTag("demand", trade.getDemand()); - recipe.intTag("tier", packet.getVillagerLevel() - 1); - recipe.tag(getItemTag(session, trade.getFirstInput(), "buyA", trade.getSpecialPrice())); + NbtMapBuilder recipe = NbtMap.builder(); + recipe.putInt("maxUses", trade.getMaxUses()); + recipe.putInt("traderExp", trade.getXp()); + recipe.putFloat("priceMultiplierA", trade.getPriceMultiplier()); + recipe.put("sell", getItemTag(session, trade.getOutput(), 0)); + recipe.putFloat("priceMultiplierB", 0.0f); + recipe.putInt("buyCountB", trade.getSecondInput() != null ? trade.getSecondInput().getAmount() : 0); + recipe.putInt("buyCountA", trade.getFirstInput().getAmount()); + recipe.putInt("demand", trade.getDemand()); + recipe.putInt("tier", packet.getVillagerLevel() - 1); + recipe.put("buyA", getItemTag(session, trade.getFirstInput(), trade.getSpecialPrice())); if (trade.getSecondInput() != null) { - recipe.tag(getItemTag(session, trade.getSecondInput(), "buyB", 0)); + recipe.put("buyB", getItemTag(session, trade.getSecondInput(), 0)); } - recipe.intTag("uses", trade.getNumUses()); - recipe.byteTag("rewardExp", (byte) 1); - tags.add(recipe.buildRootTag()); + recipe.putInt("uses", trade.getNumUses()); + recipe.putByte("rewardExp", (byte) 1); + tags.add(recipe.build()); } //Hidden trade to fix visual experience bug if (packet.isRegularVillager() && packet.getVillagerLevel() < 5) { - tags.add(CompoundTagBuilder.builder() - .intTag("maxUses", 0) - .intTag("traderExp", 0) - .floatTag("priceMultiplierA", 0.0f) - .floatTag("priceMultiplierB", 0.0f) - .intTag("buyCountB", 0) - .intTag("buyCountA", 0) - .intTag("demand", 0) - .intTag("tier", 5) - .intTag("uses", 0) - .byteTag("rewardExp", (byte) 0) - .buildRootTag()); + tags.add(NbtMap.builder() + .putInt("maxUses", 0) + .putInt("traderExp", 0) + .putFloat("priceMultiplierA", 0.0f) + .putFloat("priceMultiplierB", 0.0f) + .putInt("buyCountB", 0) + .putInt("buyCountA", 0) + .putInt("demand", 0) + .putInt("tier", 5) + .putInt("uses", 0) + .putByte("rewardExp", (byte) 0) + .build()); } - builder.listTag("Recipes", CompoundTag.class, tags); - List expTags = new ArrayList<>(); - expTags.add(CompoundTagBuilder.builder().intTag("0", 0).buildRootTag()); - expTags.add(CompoundTagBuilder.builder().intTag("1", 10).buildRootTag()); - expTags.add(CompoundTagBuilder.builder().intTag("2", 70).buildRootTag()); - expTags.add(CompoundTagBuilder.builder().intTag("3", 150).buildRootTag()); - expTags.add(CompoundTagBuilder.builder().intTag("4", 250).buildRootTag()); - builder.listTag("TierExpRequirements", CompoundTag.class, expTags); - updateTradePacket.setOffers(builder.buildRootTag()); + builder.putList("Recipes", NbtType.COMPOUND, tags); + List expTags = new ArrayList<>(); + expTags.add(NbtMap.builder().putInt("0", 0).build()); + expTags.add(NbtMap.builder().putInt("1", 10).build()); + expTags.add(NbtMap.builder().putInt("2", 70).build()); + expTags.add(NbtMap.builder().putInt("3", 150).build()); + expTags.add(NbtMap.builder().putInt("4", 250).build()); + builder.putList("TierExpRequirements", NbtType.COMPOUND, expTags); + updateTradePacket.setOffers(builder.build()); session.sendUpstreamPacket(updateTradePacket); } - private CompoundTag getItemTag(GeyserSession session, ItemStack stack, String name, int specialPrice) { + private NbtMap getItemTag(GeyserSession session, ItemStack stack, int specialPrice) { ItemData itemData = ItemTranslator.translateToBedrock(session, stack); ItemEntry itemEntry = ItemRegistry.getItem(stack); - CompoundTagBuilder builder = CompoundTagBuilder.builder(); - builder.byteTag("Count", (byte) (Math.max(itemData.getCount() + specialPrice, 1))); - builder.shortTag("Damage", itemData.getDamage()); - builder.shortTag("id", (short) itemEntry.getBedrockId()); + NbtMapBuilder builder = NbtMap.builder(); + builder.putByte("Count", (byte) (Math.max(itemData.getCount() + specialPrice, 1))); + builder.putShort("Damage", itemData.getDamage()); + builder.putShort("id", (short) itemEntry.getBedrockId()); if (itemData.getTag() != null) { - CompoundTag tag = itemData.getTag().toBuilder().build("tag"); - builder.tag(tag); + NbtMap tag = itemData.getTag().toBuilder().build(); + builder.put("tag", tag); } - return builder.build(name); + return builder.build(); } } 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 9dcfe485ac6..53607317a66 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 @@ -26,7 +26,7 @@ package org.geysermc.connector.network.translators.world.block; import com.fasterxml.jackson.databind.JsonNode; -import com.nukkitx.nbt.tag.CompoundTag; +import com.nukkitx.nbt.NbtMap; import it.unimi.dsi.fastutil.ints.*; import java.util.HashMap; @@ -41,7 +41,7 @@ public class BlockStateValues { private static final Int2ByteMap BED_COLORS = new Int2ByteOpenHashMap(); private static final Int2ObjectMap DOUBLE_CHEST_VALUES = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap FLOWER_POT_VALUES = new Int2ObjectOpenHashMap<>(); - private static final Map FLOWER_POT_BLOCKS = new HashMap<>(); + private static final Map FLOWER_POT_BLOCKS = new HashMap<>(); private static final Int2IntMap NOTEBLOCK_PITCHES = new Int2IntOpenHashMap(); private static final Int2BooleanMap IS_STICKY_PISTON = new Int2BooleanOpenHashMap(); private static final Int2BooleanMap PISTON_VALUES = new Int2BooleanOpenHashMap(); @@ -159,7 +159,7 @@ public static Int2ObjectMap getFlowerPotValues() { * Get the map of contained flower pot plants to Bedrock CompoundTag * @return Map of flower pot blocks. */ - public static Map getFlowerPotBlocks() { + public static Map getFlowerPotBlocks() { return FLOWER_POT_BLOCKS; } 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 7d95218256e..e627b84547e 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 @@ -28,11 +28,12 @@ import com.fasterxml.jackson.databind.JsonNode; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; -import com.nukkitx.nbt.CompoundTagBuilder; +import com.nukkitx.nbt.NBTInputStream; +import com.nukkitx.nbt.NbtList; +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtMapBuilder; +import com.nukkitx.nbt.NbtType; import com.nukkitx.nbt.NbtUtils; -import com.nukkitx.nbt.stream.NBTInputStream; -import com.nukkitx.nbt.tag.CompoundTag; -import com.nukkitx.nbt.tag.ListTag; import it.unimi.dsi.fastutil.ints.*; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; @@ -45,7 +46,7 @@ import java.util.*; public class BlockTranslator { - public static final ListTag BLOCKS; + public static final NbtList BLOCKS; public static final int AIR = 0; public static final int BEDROCK_WATER_ID; @@ -53,7 +54,7 @@ public class BlockTranslator { private static final Int2IntMap BEDROCK_TO_JAVA_BLOCK_MAP = new Int2IntOpenHashMap(); private static final BiMap JAVA_ID_BLOCK_MAP = HashBiMap.create(); private static final IntSet WATERLOGGED = new IntOpenHashSet(); - private static final Object2IntMap ITEM_FRAMES = new Object2IntOpenHashMap<>(); + private static final Object2IntMap ITEM_FRAMES = new Object2IntOpenHashMap<>(); // Bedrock carpet ID, used in LlamaEntity.java for decoration public static final int CARPET = 171; @@ -79,16 +80,16 @@ public class BlockTranslator { /* Load block palette */ InputStream stream = FileUtils.getResource("bedrock/runtime_block_states.dat"); - ListTag blocksTag; + NbtList blocksTag; try (NBTInputStream nbtInputStream = NbtUtils.createNetworkReader(stream)) { - blocksTag = (ListTag) nbtInputStream.readTag(); + blocksTag = (NbtList) nbtInputStream.readTag(); } catch (Exception e) { throw new AssertionError("Unable to get blocks from runtime block states", e); } - Map blockStateMap = new HashMap<>(); + Map blockStateMap = new HashMap<>(); - for (CompoundTag tag : blocksTag.getValue()) { + for (NbtMap tag : blocksTag) { if (blockStateMap.putIfAbsent(tag.getCompound("block"), tag) != null) { throw new AssertionError("Duplicate block states in Bedrock palette"); } @@ -101,9 +102,9 @@ public class BlockTranslator { } catch (Exception e) { throw new AssertionError("Unable to load Java block mappings", e); } - Object2IntMap addedStatesMap = new Object2IntOpenHashMap<>(); + Object2IntMap addedStatesMap = new Object2IntOpenHashMap<>(); addedStatesMap.defaultReturnValue(-1); - List paletteList = new ArrayList<>(); + List paletteList = new ArrayList<>(); Reflections ref = new Reflections("org.geysermc.connector.network.translators.world.block.entity"); ref.getTypesAnnotatedWith(BlockEntity.class); @@ -120,7 +121,7 @@ public class BlockTranslator { javaRuntimeId++; Map.Entry entry = blocksIterator.next(); String javaId = entry.getKey(); - CompoundTag blockTag = buildBedrockState(entry.getValue()); + NbtMap blockTag = buildBedrockState(entry.getValue()); // TODO fix this, (no block should have a null hardness) JsonNode hardnessNode = entry.getValue().get("block_hardness"); @@ -181,7 +182,7 @@ public class BlockTranslator { BEDROCK_TO_JAVA_BLOCK_MAP.putIfAbsent(bedrockRuntimeId, javaRuntimeId); } - CompoundTag runtimeTag = blockStateMap.remove(blockTag); + NbtMap runtimeTag = blockStateMap.remove(blockTag); if (runtimeTag != null) { addedStatesMap.put(blockTag, bedrockRuntimeId); paletteList.add(runtimeTag); @@ -240,15 +241,15 @@ public class BlockTranslator { // Loop around again to find all item frame runtime IDs int frameRuntimeId = 0; - for (CompoundTag tag : paletteList) { - CompoundTag blockTag = tag.getCompound("block"); + for (NbtMap tag : paletteList) { + NbtMap blockTag = tag.getCompound("block"); if (blockTag.getString("name").equals("minecraft:frame")) { ITEM_FRAMES.put(tag, frameRuntimeId); } frameRuntimeId++; } - BLOCKS = new ListTag<>("", CompoundTag.class, paletteList); + BLOCKS = new NbtList<>(NbtType.COMPOUND, paletteList); } private BlockTranslator() { @@ -258,12 +259,12 @@ public static void init() { // no-op } - private static CompoundTag buildBedrockState(JsonNode node) { - CompoundTagBuilder tagBuilder = CompoundTag.builder(); - tagBuilder.stringTag("name", node.get("bedrock_identifier").textValue()) - .intTag("version", BlockTranslator.BLOCK_STATE_VERSION); + private static NbtMap buildBedrockState(JsonNode node) { + NbtMapBuilder tagBuilder = NbtMap.builder(); + tagBuilder.putString("name", node.get("bedrock_identifier").textValue()) + .putInt("version", BlockTranslator.BLOCK_STATE_VERSION); - CompoundTagBuilder statesBuilder = CompoundTag.builder(); + NbtMapBuilder statesBuilder = NbtMap.builder(); // check for states if (node.has("bedrock_states")) { @@ -274,17 +275,18 @@ private static CompoundTag buildBedrockState(JsonNode node) { JsonNode stateValue = stateEntry.getValue(); switch (stateValue.getNodeType()) { case BOOLEAN: - statesBuilder.booleanTag(stateEntry.getKey(), stateValue.booleanValue()); + statesBuilder.putBoolean(stateEntry.getKey(), stateValue.booleanValue()); continue; case STRING: - statesBuilder.stringTag(stateEntry.getKey(), stateValue.textValue()); + statesBuilder.putString(stateEntry.getKey(), stateValue.textValue()); continue; case NUMBER: - statesBuilder.intTag(stateEntry.getKey(), stateValue.intValue()); + statesBuilder.putInt(stateEntry.getKey(), stateValue.intValue()); } } } - return tagBuilder.tag(statesBuilder.build("states")).build("block"); + tagBuilder.put("states", statesBuilder.build()); + return tagBuilder.build(); } public static int getBedrockBlockId(int state) { @@ -295,7 +297,7 @@ public static int getJavaBlockState(int bedrockId) { return BEDROCK_TO_JAVA_BLOCK_MAP.get(bedrockId); } - public static int getItemFrame(CompoundTag tag) { + public static int getItemFrame(NbtMap tag) { return ITEM_FRAMES.getOrDefault(tag, -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 15af7a70e84..e2a555090d9 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,15 +27,14 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.ListTag; -import com.nukkitx.nbt.CompoundTagBuilder; -import com.nukkitx.nbt.tag.IntTag; -import com.nukkitx.nbt.tag.StringTag; -import com.nukkitx.nbt.tag.Tag; +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtType; import org.geysermc.connector.network.translators.item.translators.BannerTranslator; import org.geysermc.connector.network.translators.world.block.BlockStateValues; import java.util.ArrayList; -import java.util.List; +import java.util.HashMap; +import java.util.Map; @BlockEntity(name = "Banner", regex = "banner") public class BannerBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { @@ -46,21 +45,21 @@ public boolean isBlock(int blockState) { } @Override - public List> translateTag(CompoundTag tag, int blockState) { - List> tags = new ArrayList<>(); + public Map translateTag(CompoundTag tag, int blockState) { + Map tags = new HashMap<>(); int bannerColor = BlockStateValues.getBannerColor(blockState); if (bannerColor != -1) { - tags.add(new IntTag("Base", 15 - bannerColor)); + tags.put("Base", 15 - bannerColor); } if (tag.contains("Patterns")) { ListTag patterns = tag.get("Patterns"); - tags.add(BannerTranslator.convertBannerPattern(patterns)); + tags.put("", BannerTranslator.convertBannerPattern(patterns)); } if (tag.contains("CustomName")) { - tags.add(new StringTag("CustomName", (String) tag.get("CustomName").getValue())); + tags.put("CustomName", tag.get("CustomName").getValue()); } return tags; @@ -74,9 +73,9 @@ public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) { } @Override - public com.nukkitx.nbt.tag.CompoundTag getDefaultBedrockTag(String bedrockId, int x, int y, int z) { - CompoundTagBuilder tagBuilder = getConstantBedrockTag(bedrockId, x, y, z).toBuilder(); - tagBuilder.listTag("Patterns", com.nukkitx.nbt.tag.CompoundTag.class, new ArrayList<>()); - return tagBuilder.buildRootTag(); + public NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z) { + return getConstantBedrockTag(bedrockId, x, y, z).toBuilder() + .putList("Patterns", NbtType.COMPOUND, new ArrayList<>()) + .build(); } } 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 31f363888db..b84aad9846e 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,13 +26,11 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import com.nukkitx.nbt.CompoundTagBuilder; -import com.nukkitx.nbt.tag.ByteTag; -import com.nukkitx.nbt.tag.Tag; +import com.nukkitx.nbt.NbtMap; import org.geysermc.connector.network.translators.world.block.BlockStateValues; -import java.util.ArrayList; -import java.util.List; +import java.util.HashMap; +import java.util.Map; @BlockEntity(name = "Bed", regex = "bed") public class BedBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { @@ -43,12 +41,12 @@ public boolean isBlock(int blockState) { } @Override - public List> translateTag(CompoundTag tag, int blockState) { - List> tags = new ArrayList<>(); + public Map translateTag(CompoundTag tag, int blockState) { + Map tags = new HashMap<>(); byte bedcolor = BlockStateValues.getBedColor(blockState); // Just in case... if (bedcolor == -1) bedcolor = 0; - tags.add(new ByteTag("color", bedcolor)); + tags.put("color", bedcolor); return tags; } @@ -58,9 +56,9 @@ public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) { } @Override - public com.nukkitx.nbt.tag.CompoundTag getDefaultBedrockTag(String bedrockId, int x, int y, int z) { - CompoundTagBuilder tagBuilder = getConstantBedrockTag(bedrockId, x, y, z).toBuilder(); - tagBuilder.byteTag("color", (byte) 0); - return tagBuilder.buildRootTag(); + public NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z) { + return getConstantBedrockTag(bedrockId, x, y, z).toBuilder() + .putByte("color", (byte) 0) + .build(); } } 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 9efda13c052..0a91cb79088 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 @@ -27,7 +27,7 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.nukkitx.math.vector.Vector3i; -import com.nukkitx.nbt.tag.CompoundTag; +import com.nukkitx.nbt.NbtMap; import org.geysermc.connector.network.session.GeyserSession; /** @@ -49,7 +49,7 @@ public interface BedrockOnlyBlockEntity { * @param blockState Java BlockState of block. * @return Bedrock tag, or null if not a Bedrock-only Block Entity */ - static CompoundTag getTag(Vector3i position, int blockState) { + static NbtMap getTag(Vector3i position, int blockState) { if (new FlowerPotBlockEntityTranslator().isBlock(blockState)) { return FlowerPotBlockEntityTranslator.getTag(blockState, position); } else if (PistonBlockEntityTranslator.isBlock(blockState)) { 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 93356e7cd83..557f37a339c 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 @@ -28,8 +28,8 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.IntTag; import com.github.steveice10.opennbt.tag.builtin.StringTag; -import com.nukkitx.nbt.CompoundTagBuilder; -import com.nukkitx.nbt.tag.Tag; +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtMapBuilder; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import org.geysermc.connector.GeyserConnector; @@ -37,7 +37,6 @@ import org.reflections.Reflections; import java.util.HashMap; -import java.util.List; import java.util.Map; public abstract class BlockEntityTranslator { @@ -87,20 +86,21 @@ public static void init() { } } - public abstract List> translateTag(CompoundTag tag, int blockState); + public abstract Map translateTag(CompoundTag tag, int blockState); public abstract CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z); - public abstract com.nukkitx.nbt.tag.CompoundTag getDefaultBedrockTag(String bedrockId, int x, int y, int z); + public abstract NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z); - public com.nukkitx.nbt.tag.CompoundTag getBlockEntityTag(String id, 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())); - CompoundTagBuilder tagBuilder = getConstantBedrockTag(BlockEntityUtils.getBedrockBlockEntityId(id), x, y, z).toBuilder(); - translateTag(tag, blockState).forEach(tagBuilder::tag); - return tagBuilder.buildRootTag(); + NbtMapBuilder tagBuilder = getConstantBedrockTag(BlockEntityUtils.getBedrockBlockEntityId(id), x, y, z).toBuilder(); + Map translatedTags = translateTag(tag, blockState); + translatedTags.forEach(tagBuilder::put); + return tagBuilder.build(); } protected CompoundTag getConstantJavaTag(String javaId, int x, int y, int z) { @@ -112,13 +112,13 @@ protected CompoundTag getConstantJavaTag(String javaId, int x, int y, int z) { return tag; } - protected com.nukkitx.nbt.tag.CompoundTag getConstantBedrockTag(String bedrockId, int x, int y, int z) { - CompoundTagBuilder tagBuilder = CompoundTagBuilder.builder() - .intTag("x", x) - .intTag("y", y) - .intTag("z", z) - .stringTag("id", bedrockId); - return tagBuilder.buildRootTag(); + protected NbtMap getConstantBedrockTag(String bedrockId, int x, int y, int z) { + return NbtMap.builder() + .putInt("x", x) + .putInt("y", y) + .putInt("z", z) + .putString("id", bedrockId) + .build(); } @SuppressWarnings("unchecked") 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 e932d2645ea..e3d2c9f5e81 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 @@ -27,25 +27,24 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.ListTag; -import com.nukkitx.nbt.CompoundTagBuilder; -import com.nukkitx.nbt.tag.Tag; +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtMapBuilder; import org.geysermc.connector.network.translators.item.ItemEntry; import org.geysermc.connector.network.translators.item.ItemRegistry; -import java.util.ArrayList; import java.util.HashMap; -import java.util.List; +import java.util.Map; @BlockEntity(name = "Campfire", regex = "campfire") public class CampfireBlockEntityTranslator extends BlockEntityTranslator { @Override - public List> translateTag(CompoundTag tag, int blockState) { - List> tags = new ArrayList<>(); + public Map translateTag(CompoundTag tag, int blockState) { + Map tags = new HashMap<>(); ListTag items = tag.get("Items"); int i = 1; for (com.github.steveice10.opennbt.tag.builtin.Tag itemTag : items.getValue()) { - tags.add(getItem((CompoundTag) itemTag).toBuilder().build("Item" + i)); + tags.put("Item" + i, getItem((CompoundTag) itemTag)); i++; } return tags; @@ -59,22 +58,17 @@ public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) { } @Override - public com.nukkitx.nbt.tag.CompoundTag getDefaultBedrockTag(String bedrockId, int x, int y, int z) { - CompoundTagBuilder tagBuilder = getConstantBedrockTag(bedrockId, x, y, z).toBuilder(); - tagBuilder.tag(new com.nukkitx.nbt.tag.CompoundTag("Item1", new HashMap<>())); - tagBuilder.tag(new com.nukkitx.nbt.tag.CompoundTag("Item2", new HashMap<>())); - tagBuilder.tag(new com.nukkitx.nbt.tag.CompoundTag("Item3", new HashMap<>())); - tagBuilder.tag(new com.nukkitx.nbt.tag.CompoundTag("Item4", new HashMap<>())); - return tagBuilder.buildRootTag(); + public NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z) { + return getConstantBedrockTag(bedrockId, x, y, z); } - protected com.nukkitx.nbt.tag.CompoundTag getItem(CompoundTag tag) { + protected NbtMap getItem(CompoundTag tag) { ItemEntry entry = ItemRegistry.getItemEntry((String) tag.get("id").getValue()); - CompoundTagBuilder tagBuilder = CompoundTagBuilder.builder() - .shortTag("id", (short) entry.getBedrockId()) - .byteTag("Count", (byte) tag.get("Count").getValue()) - .shortTag("Damage", (short) entry.getBedrockData()) - .tag(CompoundTagBuilder.builder().build("tag")); - return tagBuilder.buildRootTag(); + NbtMapBuilder tagBuilder = NbtMap.builder() + .putShort("id", (short) entry.getBedrockId()) + .putByte("Count", (byte) tag.get("Count").getValue()) + .putShort("Damage", (short) entry.getBedrockData()); + tagBuilder.put("tag", NbtMap.builder().build()); + return tagBuilder.build(); } } 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 d1afd19e03e..012d0c59566 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 @@ -28,17 +28,15 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.math.vector.Vector3i; -import com.nukkitx.nbt.CompoundTagBuilder; -import com.nukkitx.nbt.tag.ByteTag; -import com.nukkitx.nbt.tag.IntTag; -import com.nukkitx.nbt.tag.Tag; +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtMapBuilder; import org.geysermc.connector.network.session.GeyserSession; 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.ArrayList; -import java.util.List; +import java.util.HashMap; +import java.util.Map; /** * Chests have more block entity properties in Bedrock, which is solved by implementing the BedrockOnlyBlockEntity @@ -54,14 +52,14 @@ public boolean isBlock(int blockState) { @Override public void updateBlock(GeyserSession session, int blockState, Vector3i position) { CompoundTag javaTag = getConstantJavaTag("chest", position.getX(), position.getY(), position.getZ()); - CompoundTagBuilder tagBuilder = getConstantBedrockTag(BlockEntityUtils.getBedrockBlockEntityId("chest"), position.getX(), position.getY(), position.getZ()).toBuilder(); - translateTag(javaTag, blockState).forEach(tagBuilder::tag); - BlockEntityUtils.updateBlockEntity(session, tagBuilder.buildRootTag(), position); + NbtMapBuilder tagBuilder = getConstantBedrockTag(BlockEntityUtils.getBedrockBlockEntityId("chest"), position.getX(), position.getY(), position.getZ()).toBuilder(); + translateTag(javaTag, blockState).forEach(tagBuilder::put); + BlockEntityUtils.updateBlockEntity(session, tagBuilder.build(), position); } @Override - public List> translateTag(CompoundTag tag, int blockState) { - List> tags = new ArrayList<>(); + 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) { @@ -85,10 +83,10 @@ public List> translateTag(CompoundTag tag, int blockState) { x = x + (chestValues.isLeft ? 1 : -1); } } - tags.add(new IntTag("pairx", x)); - tags.add(new IntTag("pairz", z)); + tags.put("pairx", x); + tags.put("pairz", z); if (!chestValues.isLeft) { - tags.add(new ByteTag("pairlead", (byte) 1)); + tags.put("pairlead", (byte) 1); } } } @@ -101,7 +99,7 @@ public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) { } @Override - public com.nukkitx.nbt.tag.CompoundTag getDefaultBedrockTag(String bedrockId, int x, int y, int z) { + public NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z) { return null; } } 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 401bb343937..6de136119d7 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,17 +26,17 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import com.nukkitx.nbt.tag.Tag; +import com.nukkitx.nbt.NbtMap; -import java.util.ArrayList; -import java.util.List; +import java.util.HashMap; +import java.util.Map; @BlockEntity(name = "Empty", regex = "") public class EmptyBlockEntityTranslator extends BlockEntityTranslator { @Override - public List> translateTag(CompoundTag tag, int blockState) { - return new ArrayList<>(); + public Map translateTag(CompoundTag tag, int blockState) { + return new HashMap<>(); } @Override @@ -45,7 +45,7 @@ public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) { } @Override - public com.nukkitx.nbt.tag.CompoundTag getDefaultBedrockTag(String bedrockId, int x, int y, int z) { + public NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z) { return getConstantBedrockTag(bedrockId, x, y, z); } } 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 17e533bc0e0..784afed5bc2 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 @@ -27,31 +27,32 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.LongTag; -import com.nukkitx.nbt.CompoundTagBuilder; -import com.nukkitx.nbt.tag.IntTag; -import com.nukkitx.nbt.tag.Tag; +import com.nukkitx.nbt.NbtList; +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtType; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; -import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; import java.util.LinkedHashMap; -import java.util.List; +import java.util.Map; @BlockEntity(name = "EndGateway", regex = "end_gateway") public class EndGatewayBlockEntityTranslator extends BlockEntityTranslator { @Override - public List> translateTag(CompoundTag tag, int blockState) { - List> tags = new ArrayList<>(); - tags.add(new IntTag("Age", (int) (long) tag.get("Age").getValue())); + public Map translateTag(CompoundTag tag, int blockState) { + Map tags = new HashMap<>(); + tags.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 - List tagsList = new ArrayList<>(); + IntList tagsList = new IntArrayList(); // Yes, the axis letters are capitalized - tagsList.add(new IntTag("", getExitPortalCoordinate(tag, "X"))); - tagsList.add(new IntTag("", getExitPortalCoordinate(tag, "Y"))); - tagsList.add(new IntTag("", getExitPortalCoordinate(tag, "Z"))); - com.nukkitx.nbt.tag.ListTag exitPortal = - new com.nukkitx.nbt.tag.ListTag<>("ExitPortal", IntTag.class, tagsList); - tags.add(exitPortal); + 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; } @@ -63,20 +64,16 @@ public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) { } @Override - public com.nukkitx.nbt.tag.CompoundTag getDefaultBedrockTag(String bedrockId, int x, int y, int z) { - CompoundTagBuilder tagBuilder = getConstantBedrockTag(bedrockId, x, y, z).toBuilder(); - List tagsList = new ArrayList<>(); - tagsList.add(new IntTag("", 0)); - tagsList.add(new IntTag("", 0)); - tagsList.add(new IntTag("", 0)); - tagBuilder.listTag("ExitPortal", IntTag.class, tagsList); - return tagBuilder.buildRootTag(); + public NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z) { + return getConstantBedrockTag(bedrockId, x, y, z).toBuilder() + .putList("ExitPortal", NbtType.INT, Arrays.asList(0, 0, 0)) + .build(); } private int getExitPortalCoordinate(CompoundTag tag, String axis) { // Return 0 if it doesn't exist, otherwise give proper value if (tag.get("ExitPortal") != null) { - LinkedHashMap compoundTag = (LinkedHashMap) tag.get("ExitPortal").getValue(); + LinkedHashMap compoundTag = (LinkedHashMap) tag.get("ExitPortal").getValue(); com.github.steveice10.opennbt.tag.builtin.IntTag intTag = (com.github.steveice10.opennbt.tag.builtin.IntTag) compoundTag.get(axis); return intTag.getValue(); } 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 7bc199768ce..1e7cda5ff09 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 @@ -27,8 +27,8 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.nukkitx.math.vector.Vector3i; -import com.nukkitx.nbt.CompoundTagBuilder; -import com.nukkitx.nbt.tag.CompoundTag; +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.block.BlockStateValues; @@ -62,23 +62,23 @@ public void updateBlock(GeyserSession session, int blockState, Vector3i position * @param position Bedrock position of flower pot. * @return Bedrock tag of flower pot. */ - public static CompoundTag getTag(int blockState, Vector3i position) { - CompoundTagBuilder tagBuilder = CompoundTagBuilder.builder() - .intTag("x", position.getX()) - .intTag("y", position.getY()) - .intTag("z", position.getZ()) - .byteTag("isMovable", (byte) 1) - .stringTag("id", "FlowerPot"); + public static NbtMap getTag(int blockState, Vector3i position) { + NbtMapBuilder tagBuilder = NbtMap.builder() + .putInt("x", position.getX()) + .putInt("y", position.getY()) + .putInt("z", position.getZ()) + .putByte("isMovable", (byte) 1) + .putString("id", "FlowerPot"); // Get the Java name of the plant inside. e.g. minecraft:oak_sapling String name = BlockStateValues.getFlowerPotValues().get(blockState); if (name != null) { // Get the Bedrock CompoundTag of the block. // This is where we need to store the *Java* name because Bedrock has six minecraft:sapling blocks with different block states. - CompoundTag plant = BlockStateValues.getFlowerPotBlocks().get(name); + NbtMap plant = BlockStateValues.getFlowerPotBlocks().get(name); if (plant != null) { - tagBuilder.tag(plant.toBuilder().build("PlantBlock")); + tagBuilder.put("PlantBlock", plant.toBuilder().build()); } } - return tagBuilder.buildRootTag(); + return tagBuilder.build(); } } 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 bf8fcb1321f..a2362e8119d 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 @@ -27,8 +27,8 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.nukkitx.math.vector.Vector3i; -import com.nukkitx.nbt.CompoundTagBuilder; -import com.nukkitx.nbt.tag.CompoundTag; +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtMapBuilder; import org.geysermc.connector.network.translators.world.block.BlockStateValues; /** @@ -51,21 +51,21 @@ public static boolean isBlock(int blockState) { * @param position Bedrock position of piston. * @return Bedrock tag of piston. */ - public static CompoundTag getTag(int blockState, Vector3i position) { - CompoundTagBuilder tagBuilder = CompoundTagBuilder.builder() - .intTag("x", position.getX()) - .intTag("y", position.getY()) - .intTag("z", position.getZ()) - .byteTag("isMovable", (byte) 1) - .stringTag("id", "PistonArm"); + public static NbtMap getTag(int blockState, Vector3i position) { + NbtMapBuilder tagBuilder = NbtMap.builder() + .putInt("x", position.getX()) + .putInt("y", position.getY()) + .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.floatTag("Progress", (extended) ? 1.0f : 0.0f); + tagBuilder.putFloat("Progress", (extended) ? 1.0f : 0.0f); // 1 if sticky, 0 if not - tagBuilder.byteTag("Sticky", (byte)((BlockStateValues.isStickyPiston(blockState)) ? 1 : 0)); + tagBuilder.putByte("Sticky", (byte)((BlockStateValues.isStickyPiston(blockState)) ? 1 : 0)); } - return tagBuilder.buildRootTag(); + 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 b92b604e08b..329b3a8ae6a 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 @@ -27,26 +27,23 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import com.nukkitx.nbt.CompoundTagBuilder; -import com.nukkitx.nbt.tag.ByteTag; -import com.nukkitx.nbt.tag.Tag; +import com.nukkitx.nbt.NbtMap; import org.geysermc.connector.network.translators.world.block.BlockStateValues; -import java.util.ArrayList; -import java.util.List; +import java.util.HashMap; +import java.util.Map; @BlockEntity(name = "ShulkerBox", regex = "shulker_box") public class ShulkerBoxBlockEntityTranslator extends BlockEntityTranslator { @Override - public List> translateTag(CompoundTag tag, int blockState) { - List> tags = new ArrayList<>(); + public Map translateTag(CompoundTag tag, int blockState) { + Map tags = new HashMap<>(); byte direction = BlockStateValues.getShulkerBoxDirection(blockState); // Just in case... if (direction == -1) direction = 1; - tags.add(new ByteTag("facing", direction)); - + tags.put("facing", direction); return tags; } @@ -56,9 +53,9 @@ public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) { } @Override - public com.nukkitx.nbt.tag.CompoundTag getDefaultBedrockTag(String bedrockId, int x, int y, int z) { - CompoundTagBuilder tagBuilder = getConstantBedrockTag(bedrockId, x, y, z).toBuilder(); - tagBuilder.byteTag("facing", (byte)1); - return tagBuilder.buildRootTag(); + public NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z) { + return getConstantBedrockTag(bedrockId, x, y, z).toBuilder() + .putByte("facing", (byte) 1) + .build(); } } 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 d0388f335e4..a95c853e798 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,20 +27,18 @@ import com.github.steveice10.mc.protocol.data.message.MessageSerializer; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import com.nukkitx.nbt.CompoundTagBuilder; -import com.nukkitx.nbt.tag.StringTag; -import com.nukkitx.nbt.tag.Tag; +import com.nukkitx.nbt.NbtMap; import org.geysermc.connector.utils.MessageUtils; -import java.util.ArrayList; -import java.util.List; +import java.util.HashMap; +import java.util.Map; @BlockEntity(name = "Sign", regex = "sign") public class SignBlockEntityTranslator extends BlockEntityTranslator { @Override - public List> translateTag(CompoundTag tag, int blockState) { - List> tags = new ArrayList<>(); + public Map translateTag(CompoundTag tag, int blockState) { + Map tags = new HashMap<>(); StringBuilder signText = new StringBuilder(); for(int i = 0; i < 4; i++) { @@ -57,7 +55,7 @@ public List> translateTag(CompoundTag tag, int blockState) { signText.append("\n"); } - tags.add(new StringTag("Text", MessageUtils.getBedrockMessage(MessageSerializer.fromString(signText.toString())))); + tags.put("Text", MessageUtils.getBedrockMessage(MessageSerializer.fromString(signText.toString()))); return tags; } @@ -72,9 +70,9 @@ public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) { } @Override - public com.nukkitx.nbt.tag.CompoundTag getDefaultBedrockTag(String bedrockId, int x, int y, int z) { - CompoundTagBuilder tagBuilder = getConstantBedrockTag(bedrockId, x, y, z).toBuilder(); - tagBuilder.stringTag("Text", ""); - return tagBuilder.buildRootTag(); + public NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z) { + return getConstantBedrockTag(bedrockId, x, y, z).toBuilder() + .putString("Text", "") + .build(); } } 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 f868ff08895..9547ba2ff9e 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,15 +25,11 @@ package org.geysermc.connector.network.translators.world.block.entity; -import com.nukkitx.nbt.CompoundTagBuilder; -import com.nukkitx.nbt.tag.ByteTag; -import com.nukkitx.nbt.tag.CompoundTag; -import com.nukkitx.nbt.tag.FloatTag; -import com.nukkitx.nbt.tag.Tag; +import com.nukkitx.nbt.NbtMap; import org.geysermc.connector.network.translators.world.block.BlockStateValues; -import java.util.ArrayList; -import java.util.List; +import java.util.HashMap; +import java.util.Map; @BlockEntity(name = "Skull", regex = "skull") public class SkullBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { @@ -44,14 +40,14 @@ public boolean isBlock(int blockState) { } @Override - public List> translateTag(com.github.steveice10.opennbt.tag.builtin.CompoundTag tag, int blockState) { - List> tags = new ArrayList<>(); + public Map translateTag(com.github.steveice10.opennbt.tag.builtin.CompoundTag tag, int blockState) { + Map tags = new HashMap<>(); byte skullVariant = BlockStateValues.getSkullVariant(blockState); float rotation = BlockStateValues.getSkullRotation(blockState) * 22.5f; // Just in case... if (skullVariant == -1) skullVariant = 0; - tags.add(new FloatTag("Rotation", rotation)); - tags.add(new ByteTag("SkullType", skullVariant)); + tags.put("Rotation", rotation); + tags.put("SkullType", skullVariant); return tags; } @@ -61,10 +57,10 @@ public com.github.steveice10.opennbt.tag.builtin.CompoundTag getDefaultJavaTag(S } @Override - public CompoundTag getDefaultBedrockTag(String bedrockId, int x, int y, int z) { - CompoundTagBuilder tagBuilder = getConstantBedrockTag(bedrockId, x, y, z).toBuilder(); - tagBuilder.floatTag("Rotation", 0); - tagBuilder.byteTag("SkullType", (byte) 0); - return tagBuilder.buildRootTag(); + public NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z) { + return getConstantBedrockTag(bedrockId, x, y, z).toBuilder() + .putFloat("Rotation", 0f) + .putByte("SkullType", (byte) 0) + .build(); } } 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 548a1ec8c44..e911feaa297 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 @@ -27,63 +27,62 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import com.nukkitx.nbt.CompoundTagBuilder; -import com.nukkitx.nbt.tag.*; +import com.nukkitx.nbt.NbtMap; import org.geysermc.connector.entity.type.EntityType; -import java.util.ArrayList; -import java.util.List; +import java.util.HashMap; +import java.util.Map; @BlockEntity(name = "MobSpawner", regex = "mob_spawner") public class SpawnerBlockEntityTranslator extends BlockEntityTranslator { @Override - public List> translateTag(CompoundTag tag, int blockState) { - List> tags = new ArrayList<>(); + public Map translateTag(CompoundTag tag, int blockState) { + Map tags = new HashMap<>(); if (tag.get("MaxNearbyEntities") != null) { - tags.add(new ShortTag("MaxNearbyEntities", (short) tag.get("MaxNearbyEntities").getValue())); + tags.put("MaxNearbyEntities", (short) tag.get("MaxNearbyEntities").getValue()); } if (tag.get("RequiredPlayerRange") != null) { - tags.add(new ShortTag("RequiredPlayerRange", (short) tag.get("RequiredPlayerRange").getValue())); + tags.put("RequiredPlayerRange", (short) tag.get("RequiredPlayerRange").getValue()); } if (tag.get("SpawnCount") != null) { - tags.add(new ShortTag("SpawnCount", (short) tag.get("SpawnCount").getValue())); + tags.put("SpawnCount", (short) tag.get("SpawnCount").getValue()); } if (tag.get("MaxSpawnDelay") != null) { - tags.add(new ShortTag("MaxSpawnDelay", (short) tag.get("MaxSpawnDelay").getValue())); + tags.put("MaxSpawnDelay", (short) tag.get("MaxSpawnDelay").getValue()); } if (tag.get("Delay") != null) { - tags.add(new ShortTag("Delay", (short) tag.get("Delay").getValue())); + tags.put("Delay", (short) tag.get("Delay").getValue()); } if (tag.get("SpawnRange") != null) { - tags.add(new ShortTag("SpawnRange", (short) tag.get("SpawnRange").getValue())); + tags.put("SpawnRange", (short) tag.get("SpawnRange").getValue()); } if (tag.get("MinSpawnDelay") != null) { - tags.add(new ShortTag("MinSpawnDelay", (short) tag.get("MinSpawnDelay").getValue())); + tags.put("MinSpawnDelay", (short) tag.get("MinSpawnDelay").getValue()); } if (tag.get("SpawnData") != null) { CompoundTag spawnData = tag.get("SpawnData"); String entityID = (String) spawnData.get("id").getValue(); - tags.add(new StringTag("EntityIdentifier", entityID)); + tags.put("EntityIdentifier", entityID); EntityType type = EntityType.getFromIdentifier(entityID); if (type != null) { - tags.add(new FloatTag("DisplayEntityWidth", type.getWidth())); - tags.add(new FloatTag("DisplayEntityHeight", type.getHeight())); - tags.add(new FloatTag("DisplayEntityScale", 1.0f)); + tags.put("DisplayEntityWidth", type.getWidth()); + tags.put("DisplayEntityHeight", type.getHeight()); + tags.put("DisplayEntityScale", 1.0f); } } - tags.add(new StringTag("id", "MobSpawner")); - tags.add(new ByteTag("isMovable", (byte) 1)); + tags.put("id", "MobSpawner"); + tags.put("isMovable", (byte) 1); return tags; } @@ -94,11 +93,10 @@ public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) { } @Override - public com.nukkitx.nbt.tag.CompoundTag getDefaultBedrockTag(String bedrockId, int x, int y, int z) { - CompoundTagBuilder tagBuilder = getConstantBedrockTag(bedrockId, x, y, z).toBuilder(); - tagBuilder.byteTag("isMovable", (byte) 1) - .stringTag("id", "MobSpawner"); - - return tagBuilder.buildRootTag(); + public NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z) { + return getConstantBedrockTag(bedrockId, x, y, z).toBuilder() + .putByte("isMovable", (byte) 1) + .putString("id", "MobSpawner") + .build(); } } 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 3a356e03131..69c4a36820e 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/BlockEntityUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/BlockEntityUtils.java @@ -2,6 +2,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.nbt.NbtMap; import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.block.entity.BlockEntityTranslator; @@ -41,11 +42,11 @@ public static BlockEntityTranslator getBlockEntityTranslator(String name) { return blockEntityTranslator; } - public static void updateBlockEntity(GeyserSession session, com.nukkitx.nbt.tag.CompoundTag blockEntity, Position position) { + public static void updateBlockEntity(GeyserSession session, NbtMap blockEntity, Position position) { updateBlockEntity(session, blockEntity, Vector3i.from(position.getX(), position.getY(), position.getZ())); } - public static void updateBlockEntity(GeyserSession session, com.nukkitx.nbt.tag.CompoundTag blockEntity, Vector3i position) { + public static void updateBlockEntity(GeyserSession session, NbtMap blockEntity, Vector3i position) { BlockEntityDataPacket blockEntityPacket = new BlockEntityDataPacket(); blockEntityPacket.setBlockPosition(position); blockEntityPacket.setData(blockEntity); 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 5c426cb9b28..06b400908b4 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java @@ -33,9 +33,9 @@ import com.github.steveice10.opennbt.tag.builtin.Tag; import com.nukkitx.math.vector.Vector2i; import com.nukkitx.math.vector.Vector3i; -import com.nukkitx.nbt.CompoundTagBuilder; +import com.nukkitx.nbt.NBTOutputStream; +import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtUtils; -import com.nukkitx.nbt.stream.NBTOutputStream; import com.nukkitx.protocol.bedrock.packet.*; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; @@ -64,7 +64,7 @@ public class ChunkUtils { */ public static final Object2IntMap CACHED_BLOCK_ENTITIES = new Object2IntOpenHashMap<>(); - private static final com.nukkitx.nbt.tag.CompoundTag EMPTY_TAG = CompoundTagBuilder.builder().buildRootTag(); + private static final NbtMap EMPTY_TAG = NbtMap.builder().build(); public static final byte[] EMPTY_LEVEL_CHUNK_DATA; static { @@ -72,7 +72,7 @@ public class ChunkUtils { outputStream.write(new byte[258]); // Biomes + Border Size + Extra Data Size try (NBTOutputStream stream = NbtUtils.createNetworkWriter(outputStream)) { - stream.write(EMPTY_TAG); + stream.writeTag(EMPTY_TAG); } EMPTY_LEVEL_CHUNK_DATA = outputStream.toByteArray(); @@ -91,7 +91,7 @@ public static ChunkData translateToBedrock(Column column) { Object2IntMap blockEntityPositions = new Object2IntOpenHashMap<>(); // Temporarily stores compound tags of Bedrock-only block entities - ObjectArrayList bedrockOnlyBlockEntities = new ObjectArrayList<>(); + ObjectArrayList bedrockOnlyBlockEntities = new ObjectArrayList<>(); for (int chunkY = 0; chunkY < chunks.length; chunkY++) { chunkData.sections[chunkY] = new ChunkSection(); @@ -131,7 +131,7 @@ public static ChunkData translateToBedrock(Column column) { } - com.nukkitx.nbt.tag.CompoundTag[] bedrockBlockEntities = new com.nukkitx.nbt.tag.CompoundTag[blockEntities.length + bedrockOnlyBlockEntities.size()]; + NbtMap[] bedrockBlockEntities = new NbtMap[blockEntities.length + bedrockOnlyBlockEntities.size()]; int i = 0; while (i < blockEntities.length) { CompoundTag tag = blockEntities[i]; @@ -162,7 +162,7 @@ public static ChunkData translateToBedrock(Column column) { bedrockBlockEntities[i] = blockEntityTranslator.getBlockEntityTag(tagName, tag, blockState); i++; } - for (com.nukkitx.nbt.tag.CompoundTag tag : bedrockOnlyBlockEntities) { + for (NbtMap tag : bedrockOnlyBlockEntities) { bedrockBlockEntities[i] = tag; i++; } @@ -270,8 +270,8 @@ public static final class ChunkData { public ChunkSection[] sections; @Getter - private com.nukkitx.nbt.tag.CompoundTag[] blockEntities = new com.nukkitx.nbt.tag.CompoundTag[0]; + private NbtMap[] blockEntities = new NbtMap[0]; @Getter - private Object2IntMap loadBlockEntitiesLater = new Object2IntOpenHashMap<>(); + private Object2IntMap loadBlockEntitiesLater = new Object2IntOpenHashMap<>(); } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java b/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java index 627c25dc325..9862159dc34 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java @@ -27,8 +27,9 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import com.nukkitx.nbt.CompoundTagBuilder; -import com.nukkitx.nbt.tag.StringTag; +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtMapBuilder; +import com.nukkitx.nbt.NbtType; import com.nukkitx.protocol.bedrock.data.inventory.ContainerId; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; @@ -136,13 +137,13 @@ public static boolean canStack(ItemData item1, ItemData item2) { * part of the inventory is unusable. */ public static ItemData createUnusableSpaceBlock(String description) { - CompoundTagBuilder root = CompoundTagBuilder.builder(); - CompoundTagBuilder display = CompoundTagBuilder.builder(); + NbtMapBuilder root = NbtMap.builder(); + NbtMapBuilder display = NbtMap.builder(); - display.stringTag("Name", ChatColor.RESET + "Unusable inventory space"); - display.listTag("Lore", StringTag.class, Collections.singletonList(new StringTag("", ChatColor.RESET + ChatColor.DARK_PURPLE + description))); + display.putString("Name", ChatColor.RESET + "Unusable inventory space"); + display.putList("Lore", NbtType.STRING, Collections.singletonList(ChatColor.RESET + ChatColor.DARK_PURPLE + description)); - root.tag(display.build("display")); - return ItemData.of(ItemRegistry.ITEM_ENTRIES.get(ItemRegistry.BARRIER_INDEX).getBedrockId(), (short) 0, 1, root.buildRootTag()); + root.put("display", display.build()); + return ItemData.of(ItemRegistry.ITEM_ENTRIES.get(ItemRegistry.BARRIER_INDEX).getBedrockId(), (short) 0, 1, root.build()); } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/ItemUtils.java b/connector/src/main/java/org/geysermc/connector/utils/ItemUtils.java index b960022242d..bb3cf0ed0fa 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/ItemUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/ItemUtils.java @@ -26,12 +26,6 @@ package org.geysermc.connector.utils; import com.github.steveice10.opennbt.tag.builtin.*; -import com.nukkitx.nbt.CompoundTagBuilder; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; public class ItemUtils { From 69d7db4493d058ebd6648d2963ff46e353c5b8ee Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sun, 5 Jul 2020 17:18:33 -0500 Subject: [PATCH 37/72] Update mappings submodule --- connector/src/main/resources/mappings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/src/main/resources/mappings b/connector/src/main/resources/mappings index a298041e008..848f16c2d54 160000 --- a/connector/src/main/resources/mappings +++ b/connector/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit a298041e008d83e38b15248ebee5a38be2bc613f +Subproject commit 848f16c2d5441dfa2bd0768e5b75f903e623d2d9 From d1e5960d695c8c064efefc4cdb11cb2e10a1740e Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sun, 5 Jul 2020 18:33:05 -0500 Subject: [PATCH 38/72] Send a dimension change upon join game packet now sent by bungeecord on 1.16 --- .../network/translators/java/JavaJoinGameTranslator.java | 7 +++++++ 1 file changed, 7 insertions(+) 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 9b44cdd372f..5c94d6afb44 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 @@ -52,6 +52,13 @@ public class JavaJoinGameTranslator extends PacketTranslator Date: Mon, 6 Jul 2020 00:35:51 +0100 Subject: [PATCH 39/72] Add Translation support (#504) Adds full multi-language support to any Bedrock-supported language. Co-authored-by: DoctorMacc --- .gitmodules | 5 +- .../bungeecord/GeyserBungeePlugin.java | 5 +- .../command/GeyserBungeeCommandExecutor.java | 11 +- .../platform/spigot/GeyserSpigotPlugin.java | 9 +- .../command/GeyserSpigotCommandExecutor.java | 12 +- .../platform/sponge/GeyserSpongePlugin.java | 5 +- .../command/GeyserSpongeCommandExecutor.java | 7 +- .../standalone/GeyserStandaloneBootstrap.java | 3 +- .../platform/standalone/LoopbackUtil.java | 9 +- .../standalone/gui/GeyserStandaloneGUI.java | 35 +-- .../velocity/GeyserVelocityPlugin.java | 5 +- .../GeyserVelocityCommandExecutor.java | 4 +- common/src/main/resources/help.txt | 18 -- .../connector/FloodgateKeyLoader.java | 7 +- .../geysermc/connector/GeyserConnector.java | 23 +- .../connector/command/CommandManager.java | 17 +- .../command/defaults/DumpCommand.java | 19 +- .../command/defaults/HelpCommand.java | 14 +- .../command/defaults/ListCommand.java | 11 +- .../command/defaults/ReloadCommand.java | 15 +- .../connector/common/main/IGeyserMain.java | 49 ++++- .../configuration/GeyserConfiguration.java | 6 +- .../network/ConnectorServerEventHandler.java | 7 +- .../network/UpstreamPacketHandler.java | 13 +- .../network/session/GeyserSession.java | 32 +-- .../network/translators/BiomeTranslator.java | 3 +- .../translators/EntityIdentifierRegistry.java | 3 +- .../translators/PacketTranslatorRegistry.java | 7 +- ...drockPacketViolationWarningTranslator.java | 1 + .../translators/effect/EffectRegistry.java | 3 +- .../inventory/PlayerInventoryTranslator.java | 4 +- .../updater/ChestInventoryUpdater.java | 4 +- .../translators/item/ItemRegistry.java | 14 +- .../translators/item/ItemTranslator.java | 6 +- .../JavaPlayerPositionRotationTranslator.java | 3 +- .../spawn/JavaSpawnEntityTranslator.java | 3 +- .../JavaSpawnLivingEntityTranslator.java | 3 +- .../spawn/JavaSpawnPlayerTranslator.java | 3 +- .../java/scoreboard/JavaTeamTranslator.java | 17 +- .../scoreboard/JavaUpdateScoreTranslator.java | 3 +- .../block/entity/BlockEntityTranslator.java | 5 +- .../connector/scoreboard/Scoreboard.java | 3 +- .../geysermc/connector/utils/DockerCheck.java | 4 +- .../geysermc/connector/utils/FileUtils.java | 2 +- .../connector/utils/InventoryUtils.java | 3 +- .../connector/utils/LanguageUtils.java | 207 ++++++++++++++++++ .../geysermc/connector/utils/LocaleUtils.java | 22 +- .../connector/utils/LoginEncryptionUtils.java | 20 +- .../geysermc/connector/utils/SkinUtils.java | 6 +- connector/src/main/resources/config.yml | 4 +- connector/src/main/resources/languages | 1 + 51 files changed, 504 insertions(+), 191 deletions(-) delete mode 100644 common/src/main/resources/help.txt create mode 100644 connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java create mode 160000 connector/src/main/resources/languages diff --git a/.gitmodules b/.gitmodules index 207825e83fb..35887822e5c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,7 @@ [submodule "connector/src/main/resources/mappings"] path = connector/src/main/resources/mappings url = https://github.com/GeyserMC/mappings.git - branch = feature/1.16 + branch = feature/1.16 +[submodule "connector/src/main/resources/languages"] + path = connector/src/main/resources/languages + url = https://github.com/GeyserMC/languages.git 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 ac718cbaee2..38d319e151c 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 @@ -39,6 +39,7 @@ import org.geysermc.connector.ping.GeyserLegacyPingPassthrough; import org.geysermc.connector.ping.IGeyserPingPassthrough; import org.geysermc.connector.utils.FileUtils; +import org.geysermc.connector.utils.LanguageUtils; import org.geysermc.platform.bungeecord.command.GeyserBungeeCommandExecutor; import org.geysermc.platform.bungeecord.command.GeyserBungeeCommandManager; @@ -71,7 +72,7 @@ public void onEnable() { this.geyserConfig = FileUtils.loadConfig(configFile, GeyserBungeeConfiguration.class); configuration = ConfigurationProvider.getProvider(YamlConfiguration.class).load(new File(getDataFolder(), "config.yml")); } catch (IOException ex) { - getLogger().log(Level.WARNING, "Failed to read/create config.yml! Make sure it's up to date and/or readable+writable!", ex); + getLogger().log(Level.WARNING, LanguageUtils.getLocaleStringLog("geyser.config.failed"), ex); ex.printStackTrace(); } @@ -93,7 +94,7 @@ public void onEnable() { GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); if (geyserConfig.getRemote().getAuthType().equals("floodgate") && getProxy().getPluginManager().getPlugin("floodgate-bungee") == null) { - geyserLogger.severe("Auth type set to Floodgate but Floodgate not found! Disabling..."); + geyserLogger.severe(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.disabling")); return; } 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 d1c8473bd96..3b051c5c371 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 @@ -33,6 +33,8 @@ 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; import java.util.Arrays; @@ -52,7 +54,14 @@ public void execute(CommandSender sender, String[] args) { if (args.length > 0) { if (getCommand(args[0]) != null) { if (!sender.hasPermission(getCommand(args[0]).getPermission())) { - sender.sendMessage(TextComponent.fromLegacyText(ChatColor.RED + "You do not have permission to execute this command!")); + 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"); + } + + sender.sendMessage(TextComponent.fromLegacyText(ChatColor.RED + message)); return; } getCommand(args[0]).execute(new BungeeCommandSender(sender), args); 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 de2b7186c5e..38141c520dc 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 @@ -37,6 +37,7 @@ import org.geysermc.connector.ping.GeyserLegacyPingPassthrough; import org.geysermc.connector.ping.IGeyserPingPassthrough; 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.world.GeyserSpigotBlockPlaceListener; @@ -68,15 +69,15 @@ public void onEnable() { getDataFolder().mkdir(); File bukkitConfig = new File("plugins/Geyser-Bukkit/config.yml"); if (bukkitConfig.exists()) { // Copy over old configs - getLogger().log(Level.INFO, "Existing config found in the Geyser-Bukkit folder; copying over..."); + getLogger().log(Level.INFO, LanguageUtils.getLocaleStringLog("geyser.bootstrap.config.copy_bukkit_config")); Files.copy(bukkitConfig.toPath(), new File(getDataFolder().toString() + "/config.yml").toPath()); - getLogger().log(Level.INFO, "Copied!"); + getLogger().log(Level.INFO, LanguageUtils.getLocaleStringLog("geyser.bootstrap.config.copied_bukkit_config")); } } File configFile = FileUtils.fileOrCopiedFromResource(new File(getDataFolder(), "config.yml"), "config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString())); this.geyserConfig = FileUtils.loadConfig(configFile, GeyserSpigotConfiguration.class); } catch (IOException ex) { - getLogger().log(Level.WARNING, "Failed to read/create config.yml! Make sure it's up to date and/or readable+writable!", ex); + getLogger().log(Level.WARNING, LanguageUtils.getLocaleStringLog("geyser.config.failed"), ex); ex.printStackTrace(); } @@ -92,7 +93,7 @@ public void onEnable() { GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); if (geyserConfig.getRemote().getAuthType().equals("floodgate") && Bukkit.getPluginManager().getPlugin("floodgate-bukkit") == null) { - geyserLogger.severe("Auth type set to Floodgate but Floodgate not found! Disabling..."); + geyserLogger.severe(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.disabling")); this.getPluginLoader().disablePlugin(this); return; } 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 b956a0d8428..38187275294 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 @@ -26,13 +26,14 @@ package org.geysermc.platform.spigot.command; import lombok.AllArgsConstructor; - import org.bukkit.ChatColor; import org.bukkit.command.Command; 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; import java.util.Arrays; @@ -48,7 +49,14 @@ public boolean onCommand(CommandSender sender, Command command, String label, St if (args.length > 0) { if (getCommand(args[0]) != null) { if (!sender.hasPermission(getCommand(args[0]).getPermission())) { - sender.sendMessage(ChatColor.RED + "You do not have permission to execute this command!"); + 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"); + } + + sender.sendMessage(ChatColor.RED + message); return true; } getCommand(args[0]).execute(new SpigotCommandSender(sender), args); 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 4214255e830..3151e973c87 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 @@ -38,6 +38,7 @@ import org.geysermc.connector.ping.GeyserLegacyPingPassthrough; import org.geysermc.connector.ping.IGeyserPingPassthrough; import org.geysermc.connector.utils.FileUtils; +import org.geysermc.connector.utils.LanguageUtils; import org.geysermc.platform.sponge.command.GeyserSpongeCommandExecutor; import org.geysermc.platform.sponge.command.GeyserSpongeCommandManager; import org.slf4j.Logger; @@ -80,7 +81,7 @@ public void onEnable() { try { configFile = FileUtils.fileOrCopiedFromResource(new File(configDir, "config.yml"), "config.yml", (file) -> file.replaceAll("generateduuid", UUID.randomUUID().toString())); } catch (IOException ex) { - logger.warn("Failed to copy config.yml from jar path!"); + logger.warn(LanguageUtils.getLocaleStringLog("geyser.config.failed")); ex.printStackTrace(); } @@ -90,7 +91,7 @@ public void onEnable() { config = loader.load(); this.geyserConfig = new GeyserSpongeConfiguration(configDir, config); } catch (IOException ex) { - logger.warn("Failed to load config.yml!"); + logger.warn(LanguageUtils.getLocaleStringLog("geyser.config.failed")); ex.printStackTrace(); return; } 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 8f857b66553..d37321ffe13 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 @@ -26,10 +26,10 @@ package org.geysermc.platform.sponge.command; import lombok.AllArgsConstructor; - -import org.geysermc.connector.common.ChatColor; import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.common.ChatColor; import org.geysermc.connector.command.GeyserCommand; +import org.geysermc.connector.utils.LanguageUtils; import org.spongepowered.api.command.CommandCallable; import org.spongepowered.api.command.CommandException; import org.spongepowered.api.command.CommandResult; @@ -55,7 +55,8 @@ public CommandResult process(CommandSource source, String arguments) throws Comm if (args.length > 0) { if (getCommand(args[0]) != null) { if (!source.hasPermission(getCommand(args[0]).getPermission())) { - source.sendMessage(Text.of(ChatColor.RED + "You do not have permission to execute this command!")); + // Not ideal to use log here but we dont get a session + source.sendMessage(Text.of(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.bootstrap.command.permission_fail"))); return CommandResult.success(); } getCommand(args[0]).execute(new SpongeCommandSender(source), args); 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 762b09ba5fc..5da4c37d9d5 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 @@ -40,6 +40,7 @@ import org.geysermc.connector.ping.GeyserLegacyPingPassthrough; import org.geysermc.connector.ping.IGeyserPingPassthrough; import org.geysermc.connector.utils.FileUtils; +import org.geysermc.connector.utils.LanguageUtils; import org.geysermc.platform.standalone.command.GeyserCommandManager; import org.geysermc.platform.standalone.gui.GeyserStandaloneGUI; @@ -107,7 +108,7 @@ public void onEnable() { File configFile = FileUtils.fileOrCopiedFromResource("config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString())); geyserConfig = FileUtils.loadConfig(configFile, GeyserStandaloneConfiguration.class); } catch (IOException ex) { - geyserLogger.severe("Failed to read/create config.yml! Make sure it's up to date and/or readable+writable!", ex); + geyserLogger.severe(LanguageUtils.getLocaleStringLog("geyser.config.failed"), ex); System.exit(0); } GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/LoopbackUtil.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/LoopbackUtil.java index 00ff14de731..8c54d141b69 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/LoopbackUtil.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/LoopbackUtil.java @@ -1,12 +1,13 @@ package org.geysermc.platform.standalone; +import org.geysermc.connector.common.ChatColor; +import org.geysermc.connector.utils.LanguageUtils; + import java.io.InputStream; import java.nio.file.Files; import java.nio.file.OpenOption; import java.nio.file.Paths; -import org.geysermc.connector.common.ChatColor; - public class LoopbackUtil { private static final String checkExemption = "powershell -Command \"CheckNetIsolation LoopbackExempt -s\""; // Java's Exec feature runs as CMD, NetIsolation is only accessible from PowerShell. private static final String loopbackCommand = "powershell -Command \"CheckNetIsolation LoopbackExempt -a -n='Microsoft.MinecraftUWP_8wekyb3d8bbwe'\""; @@ -31,12 +32,12 @@ public static void checkLoopback(GeyserStandaloneLogger geyserLogger) { Files.write(Paths.get(System.getenv("temp") + "/loopback_minecraft.bat"), loopbackCommand.getBytes(), new OpenOption[0]); process = Runtime.getRuntime().exec(startScript); - geyserLogger.info(ChatColor.AQUA + "Added loopback exemption to Windows!"); + geyserLogger.info(ChatColor.AQUA + LanguageUtils.getLocaleStringLog("geyser.bootstrap.loopback.added")); } } catch (Exception e) { e.printStackTrace(); - geyserLogger.error("Couldn't auto add loopback exemption to Windows!"); + geyserLogger.error(LanguageUtils.getLocaleStringLog("geyser.bootstrap.loopback.failed")); } } } 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 f4138354db9..5a6b4c89da0 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 @@ -29,6 +29,7 @@ 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 org.geysermc.platform.standalone.GeyserStandaloneLogger; import org.geysermc.platform.standalone.command.GeyserCommandManager; @@ -52,7 +53,9 @@ public class GeyserStandaloneGUI { - private static final String[] playerTableHeadings = new String[] {"IP", "Username"}; + private static final String[] playerTableHeadings = new String[] { + LanguageUtils.getLocaleStringLog("geyser.gui.table.ip"), + LanguageUtils.getLocaleStringLog("geyser.gui.table.username")}; private static final List ramValues = new ArrayList<>(); private static final ColorPane consolePane = new ColorPane(); @@ -67,7 +70,7 @@ public class GeyserStandaloneGUI { public GeyserStandaloneGUI() { // Create the frame and setup basic settings - JFrame frame = new JFrame("Geyser Standalone"); + JFrame frame = new JFrame(LanguageUtils.getLocaleStringLog("geyser.gui.title")); frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); frame.setSize(800, 400); frame.setMinimumSize(frame.getSize()); @@ -82,8 +85,8 @@ public GeyserStandaloneGUI() { @Override public void windowClosing(WindowEvent we) { - String[] buttons = {"Yes", "No"}; - int result = JOptionPane.showOptionDialog(frame, "Are you sure you want to exit?", frame.getTitle(), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null, buttons, buttons[1]); + String[] buttons = {LanguageUtils.getLocaleStringLog("geyser.gui.exit.confirm"), LanguageUtils.getLocaleStringLog("geyser.gui.exit.deny")}; + int result = JOptionPane.showOptionDialog(frame, LanguageUtils.getLocaleStringLog("geyser.gui.exit.message"), frame.getTitle(), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null, buttons, buttons[1]); if (result == JOptionPane.YES_OPTION) { System.exit(0); } @@ -124,12 +127,12 @@ public void componentResized(ComponentEvent e) { JMenuBar menuBar = new JMenuBar(); // Create 'File' - JMenu fileMenu = new JMenu("File"); + JMenu fileMenu = new JMenu(LanguageUtils.getLocaleStringLog("geyser.gui.menu.file")); fileMenu.setMnemonic(KeyEvent.VK_F); menuBar.add(fileMenu); // 'Open Geyser folder' button - JMenuItem openButton = new JMenuItem("Open Geyser folder", KeyEvent.VK_O); + JMenuItem openButton = new JMenuItem(LanguageUtils.getLocaleStringLog("geyser.gui.menu.file.open_folder"), KeyEvent.VK_O); openButton.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, InputEvent.CTRL_MASK)); openButton.addActionListener(e -> { try { @@ -141,40 +144,40 @@ public void componentResized(ComponentEvent e) { fileMenu.addSeparator(); // 'Exit' button - JMenuItem exitButton = new JMenuItem("Exit", KeyEvent.VK_X); + JMenuItem exitButton = new JMenuItem(LanguageUtils.getLocaleStringLog("geyser.gui.menu.file.exit"), KeyEvent.VK_X); exitButton.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F4, InputEvent.ALT_MASK)); exitButton.addActionListener(e -> System.exit(0)); fileMenu.add(exitButton); // Create 'Commands' - commandsMenu = new JMenu("Commands"); + commandsMenu = new JMenu(LanguageUtils.getLocaleStringLog("geyser.gui.menu.commands")); commandsMenu.setMnemonic(KeyEvent.VK_C); menuBar.add(commandsMenu); // Create 'View' - JMenu viewMenu = new JMenu("View"); + JMenu viewMenu = new JMenu(LanguageUtils.getLocaleStringLog("geyser.gui.menu.view")); viewMenu.setMnemonic(KeyEvent.VK_V); menuBar.add(viewMenu); // 'Zoom in' button - JMenuItem zoomInButton = new JMenuItem("Zoom In"); + JMenuItem zoomInButton = new JMenuItem(LanguageUtils.getLocaleStringLog("geyser.gui.menu.view.zoom_in")); zoomInButton.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_EQUALS, InputEvent.CTRL_DOWN_MASK)); zoomInButton.addActionListener(e -> consolePane.setFont(new Font(consolePane.getFont().getName(), consolePane.getFont().getStyle(), consolePane.getFont().getSize() + 1))); viewMenu.add(zoomInButton); // 'Zoom in' button - JMenuItem zoomOutButton = new JMenuItem("Zoom Out"); + JMenuItem zoomOutButton = new JMenuItem(LanguageUtils.getLocaleStringLog("geyser.gui.menu.view.zoom_out")); zoomOutButton.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, InputEvent.CTRL_DOWN_MASK)); zoomOutButton.addActionListener(e -> consolePane.setFont(new Font(consolePane.getFont().getName(), consolePane.getFont().getStyle(), consolePane.getFont().getSize() - 1))); viewMenu.add(zoomOutButton); // 'Reset Zoom' button - JMenuItem resetZoomButton = new JMenuItem("Reset Zoom"); + JMenuItem resetZoomButton = new JMenuItem(LanguageUtils.getLocaleStringLog("geyser.gui.menu.view.reset_zoom")); resetZoomButton.addActionListener(e -> consolePane.setFont(new Font(consolePane.getFont().getName(), consolePane.getFont().getStyle(), originalFontSize))); viewMenu.add(resetZoomButton); // create 'Options' - optionsMenu = new JMenu("Options"); + optionsMenu = new JMenu(LanguageUtils.getLocaleStringLog("geyser.gui.menu.options")); viewMenu.setMnemonic(KeyEvent.VK_O); menuBar.add(optionsMenu); @@ -195,7 +198,7 @@ public void componentResized(ComponentEvent e) { ramValues.add(0); } ramGraph.setValues(ramValues); - ramGraph.setXLabel("Loading..."); + ramGraph.setXLabel(LanguageUtils.getLocaleStringLog("geyser.gui.graph.loading")); rightContentPane.add(ramGraph); JScrollPane playerScrollPane = new JScrollPane(playerTable); @@ -270,7 +273,7 @@ public void setupInterface(GeyserStandaloneLogger geyserStandaloneLogger, Geyser } // 'Debug Mode' toggle - JCheckBoxMenuItem debugMode = new JCheckBoxMenuItem("Debug Mode"); + JCheckBoxMenuItem debugMode = new JCheckBoxMenuItem(LanguageUtils.getLocaleStringLog("geyser.gui.menu.options.toggle_debug_mode")); debugMode.setSelected(geyserStandaloneLogger.isDebug()); debugMode.addActionListener(e -> geyserStandaloneLogger.setDebug(!geyserStandaloneLogger.isDebug())); optionsMenu.add(debugMode); @@ -305,7 +308,7 @@ public void startUpdateThread() { final int freePercent = (int)(freeMemory * 100.0 / totalMemory + 0.5); ramValues.add(100 - freePercent); - ramGraph.setXLabel("Usage: " + String.format("%,d", (totalMemory - freeMemory) / MEGABYTE) + "mb (" + freePercent + "% free)"); + ramGraph.setXLabel(LanguageUtils.getLocaleStringLog("geyser.gui.graph.usage", String.format("%,d", (totalMemory - freeMemory) / MEGABYTE), freePercent)); // Trim the list int k = ramValues.size(); 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 f00119ca932..425aba183fe 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 @@ -43,6 +43,7 @@ import org.geysermc.connector.ping.GeyserLegacyPingPassthrough; import org.geysermc.connector.ping.IGeyserPingPassthrough; import org.geysermc.connector.utils.FileUtils; +import org.geysermc.connector.utils.LanguageUtils; import org.geysermc.platform.velocity.command.GeyserVelocityCommandExecutor; import org.geysermc.platform.velocity.command.GeyserVelocityCommandManager; import org.slf4j.Logger; @@ -85,7 +86,7 @@ public void onEnable() { File configFile = FileUtils.fileOrCopiedFromResource(configFolder.resolve("config.yml").toFile(), "config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString())); this.geyserConfig = FileUtils.loadConfig(configFile, GeyserVelocityConfiguration.class); } catch (IOException ex) { - logger.warn("Failed to read/create config.yml! Make sure it's up to date and/or readable+writable!", ex); + logger.warn(LanguageUtils.getLocaleStringLog("geyser.config.failed"), ex); ex.printStackTrace(); } @@ -103,7 +104,7 @@ public void onEnable() { GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); if (geyserConfig.getRemote().getAuthType().equals("floodgate") && !proxyServer.getPluginManager().getPlugin("floodgate").isPresent()) { - geyserLogger.severe("Auth type set to Floodgate but Floodgate not found! Disabling..."); + geyserLogger.severe(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.disabling")); return; } 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 4632f4404bf..fa3aaa3c3c2 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 @@ -35,6 +35,7 @@ import org.geysermc.connector.common.ChatColor; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.GeyserCommand; +import org.geysermc.connector.utils.LanguageUtils; @AllArgsConstructor public class GeyserVelocityCommandExecutor implements Command { @@ -46,7 +47,8 @@ public void execute(CommandSource source, String[] args) { if (args.length > 0) { if (getCommand(args[0]) != null) { if (!source.hasPermission(getCommand(args[0]).getPermission())) { - source.sendMessage(TextComponent.of(ChatColor.RED + "You do not have permission to execute this command!")); + // 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"))); return; } getCommand(args[0]).execute(new VelocityCommandSender(source), args); diff --git a/common/src/main/resources/help.txt b/common/src/main/resources/help.txt deleted file mode 100644 index 3512ed83948..00000000000 --- a/common/src/main/resources/help.txt +++ /dev/null @@ -1,18 +0,0 @@ - --------------------------------------------------------------------------------- - - Oops! You attempted to run a plugin version of Geyser directly! - - This jar file is a plugin for ${plugin_type}. You can run this file as a - plugin by dropping the jar file into the "${plugin_folder}" directory. - - There is also a standalone version available that doesn't need to - be installed as a plugin, you can find it on our build server: - - http://ci.geysermc.org/ - - If you need more help, you should check out our discord: - - http://discord.geysermc.org/ - --------------------------------------------------------------------------------- \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/FloodgateKeyLoader.java b/connector/src/main/java/org/geysermc/connector/FloodgateKeyLoader.java index 617ac83eb3f..d4413d68ba4 100644 --- a/connector/src/main/java/org/geysermc/connector/FloodgateKeyLoader.java +++ b/connector/src/main/java/org/geysermc/connector/FloodgateKeyLoader.java @@ -26,6 +26,7 @@ package org.geysermc.connector; +import org.geysermc.connector.utils.LanguageUtils; import org.geysermc.connector.configuration.GeyserConfiguration; import java.nio.file.Files; @@ -37,13 +38,13 @@ public static Path getKey(GeyserLogger logger, GeyserConfiguration config, Path if (floodgate != null) { Path autoKey = floodgateFolder.resolve("public-key.pem"); if (Files.exists(autoKey)) { - logger.info("Auto-loaded floodgate key"); + logger.info(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.auto_loaded")); floodgateKey = autoKey; } else { - logger.error("Auth-type set to floodgate and the public key is missing!"); + logger.error(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.missing_key")); } } else { - logger.error("Auth-type set to floodgate but floodgate is not installed!"); + logger.error(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed")); } } diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 6cde2a2db3a..298f5bc8270 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -50,6 +50,7 @@ import org.geysermc.connector.network.translators.sound.SoundHandlerRegistry; import org.geysermc.connector.network.translators.sound.SoundRegistry; import org.geysermc.connector.network.translators.world.WorldManager; +import org.geysermc.connector.utils.LanguageUtils; import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.network.translators.world.block.entity.BlockEntityTranslator; import org.geysermc.connector.utils.DimensionUtils; @@ -107,7 +108,7 @@ private GeyserConnector(PlatformType platformType, GeyserBootstrap bootstrap) { logger.info("******************************************"); logger.info(""); - logger.info("Loading " + NAME + " version " + VERSION); + logger.info(LanguageUtils.getLocaleStringLog("geyser.core.load", NAME, VERSION)); logger.info(""); logger.info("******************************************"); @@ -143,9 +144,9 @@ private GeyserConnector(PlatformType platformType, GeyserBootstrap bootstrap) { bedrockServer.setHandler(new ConnectorServerEventHandler(this)); bedrockServer.bind().whenComplete((avoid, throwable) -> { if (throwable == null) { - logger.info("Started Geyser on " + config.getBedrock().getAddress() + ":" + config.getBedrock().getPort()); + logger.info(LanguageUtils.getLocaleStringLog("geyser.core.start", config.getBedrock().getAddress(), String.valueOf(config.getBedrock().getPort()))); } else { - logger.severe("Failed to start Geyser on " + config.getBedrock().getAddress() + ":" + config.getBedrock().getPort()); + logger.severe(LanguageUtils.getLocaleStringLog("geyser.core.fail", config.getBedrock().getAddress(), config.getBedrock().getPort())); throwable.printStackTrace(); } }).join(); @@ -168,24 +169,24 @@ private GeyserConnector(PlatformType platformType, GeyserBootstrap bootstrap) { } double completeTime = (System.currentTimeMillis() - startupTime) / 1000D; - String message = String.format("Done (%ss)!", new DecimalFormat("#.###").format(completeTime)); + String message = LanguageUtils.getLocaleStringLog("geyser.core.finish.done", new DecimalFormat("#.###").format(completeTime)) + " "; if (isGui) { - message += " Run Commands -> help for help!"; + message += LanguageUtils.getLocaleStringLog("geyser.core.finish.gui"); } else { - message += " Run /geyser help for help!"; + message += LanguageUtils.getLocaleStringLog("geyser.core.finish.console"); } logger.info(message); } public void shutdown() { - bootstrap.getGeyserLogger().info("Shutting down Geyser."); + bootstrap.getGeyserLogger().info(LanguageUtils.getLocaleStringLog("geyser.core.shutdown")); shuttingDown = true; if (players.size() >= 1) { - bootstrap.getGeyserLogger().info("Kicking " + players.size() + " player(s)"); + bootstrap.getGeyserLogger().info(LanguageUtils.getLocaleStringLog("geyser.core.shutdown.kick.log", players.size())); for (GeyserSession playerSession : players.values()) { - playerSession.disconnect("Geyser Proxy shutting down."); + playerSession.disconnect(LanguageUtils.getPlayerLocaleString("geyser.core.shutdown.kick.message", playerSession.getClientData().getLanguageCode())); } CompletableFuture future = CompletableFuture.runAsync(new Runnable() { @@ -209,7 +210,7 @@ public void run() { // Block and wait for the future to complete try { future.get(); - bootstrap.getGeyserLogger().info("Kicked all players"); + bootstrap.getGeyserLogger().info(LanguageUtils.getLocaleStringLog("geyser.core.shutdown.kick.done")); } catch (Exception e) { // Quietly fail } @@ -222,7 +223,7 @@ public void run() { authType = null; this.getCommandManager().getCommands().clear(); - bootstrap.getGeyserLogger().info("Geyser shutdown successfully."); + bootstrap.getGeyserLogger().info(LanguageUtils.getLocaleStringLog("geyser.core.shutdown.done")); } public void addPlayer(GeyserSession player) { 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 217a9df1fa3..eb75a2df4c2 100644 --- a/connector/src/main/java/org/geysermc/connector/command/CommandManager.java +++ b/connector/src/main/java/org/geysermc/connector/command/CommandManager.java @@ -29,6 +29,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; @@ -44,17 +45,17 @@ public abstract class CommandManager { public CommandManager(GeyserConnector connector) { this.connector = connector; - registerCommand(new HelpCommand(connector, "help", "Shows help for all registered commands.", "geyser.command.help")); - registerCommand(new ListCommand(connector, "list", "List all players connected through Geyser.", "geyser.command.list")); - registerCommand(new ReloadCommand(connector, "reload", "Reloads the Geyser configurations. Kicks all players when used!", "geyser.command.reload")); - registerCommand(new StopCommand(connector, "stop", "Shuts down Geyser.", "geyser.command.stop")); - registerCommand(new OffhandCommand(connector, "offhand", "Puts an items in your offhand.", "geyser.command.offhand")); - registerCommand(new DumpCommand(connector, "dump", "Dumps Geyser debug infomation for bug reports.", "geyser.command.dump")); + 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")); } public void registerCommand(GeyserCommand command) { commands.put(command.getName(), command); - connector.getLogger().debug("Registered command " + command.getName()); + connector.getLogger().debug(LanguageUtils.getLocaleStringLog("geyser.commands.registered", command.getName())); if (command.getAliases().isEmpty()) return; @@ -82,7 +83,7 @@ public void runCommand(CommandSender sender, String command) { GeyserCommand cmd = commands.get(label); if (cmd == null) { - connector.getLogger().error("Invalid Command! Try /geyser help for a list of commands."); + connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.commands.invalid")); return; } 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 617c9d43644..6566ecc1c4d 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 @@ -34,6 +34,7 @@ import org.geysermc.connector.command.CommandSender; import org.geysermc.connector.command.GeyserCommand; import org.geysermc.connector.dump.DumpInfo; +import org.geysermc.connector.utils.LanguageUtils; import org.geysermc.connector.utils.WebUtils; import java.io.IOException; @@ -57,37 +58,37 @@ public DumpCommand(GeyserConnector connector, String name, String description, S @Override public void execute(CommandSender sender, String[] args) { - sender.sendMessage("Collecting dump info"); + sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.dump.collecting")); String dumpData = ""; try { dumpData = MAPPER.writeValueAsString(new DumpInfo()); } catch (IOException e) { - sender.sendMessage(ChatColor.RED + "Failed to collect dump info, check console for more information"); - connector.getLogger().error("Failed to collect dump info", e); + sender.sendMessage(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.commands.dump.collect_error")); + connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.commands.dump.collect_error_short"), e); return; } - sender.sendMessage("Uploading dump"); + sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.dump.uploading")); String response; JsonNode responseNode; try { response = WebUtils.post(DUMP_URL + "documents", dumpData); responseNode = MAPPER.readTree(response); } catch (IOException e) { - sender.sendMessage(ChatColor.RED + "Failed to upload dump, check console for more information"); - connector.getLogger().error("Failed to upload dump", e); + sender.sendMessage(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.commands.dump.upload_error")); + connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.commands.dump.upload_error_short"), e); return; } if (!responseNode.has("key")) { - sender.sendMessage(ChatColor.RED + "Failed to upload dump: " + (responseNode.has("message") ? responseNode.get("message").asText() : response)); + sender.sendMessage(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.commands.dump.upload_error_short") + ": " + (responseNode.has("message") ? responseNode.get("message").asText() : response)); return; } String uploadedDumpUrl = DUMP_URL + responseNode.get("key").asText(); - sender.sendMessage("We've made a dump with useful information, report your issue and provide this url: " + ChatColor.DARK_AQUA + uploadedDumpUrl); + sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.dump.message") + " " + ChatColor.DARK_AQUA + uploadedDumpUrl); if (!sender.isConsole()) { - connector.getLogger().info(sender.getName() + " created a GeyserDump at " + uploadedDumpUrl); + 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 a5942ee6b90..0407cf6ef7a 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 @@ -29,6 +29,8 @@ 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.utils.LanguageUtils; import java.util.Collections; import java.util.List; @@ -48,7 +50,17 @@ public HelpCommand(GeyserConnector connector, String name, String description, S @Override public void execute(CommandSender sender, String[] args) { - sender.sendMessage("---- Showing Help For: Geyser (Page 1/1) ----"); + 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); + } + + 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())); 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 99845ee9419..0de73a5d154 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 @@ -25,11 +25,11 @@ 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.utils.LanguageUtils; import java.util.stream.Collectors; @@ -45,6 +45,13 @@ public ListCommand(GeyserConnector connector, String name, String description, S @Override public void execute(CommandSender sender, String[] args) { - sender.sendMessage(ChatColor.YELLOW + "Online Players (" + connector.getPlayers().size() + "): " + ChatColor.WHITE + connector.getPlayers().values().stream().map(GeyserSession::getName).collect(Collectors.joining(" "))); + String message = ""; + if (sender instanceof GeyserSession) { + message = LanguageUtils.getPlayerLocaleString("geyser.commands.list.message", ((GeyserSession) sender).getClientData().getLanguageCode(), connector.getPlayers().size(), connector.getPlayers().values().stream().map(GeyserSession::getName).collect(Collectors.joining(" "))); + } else { + message = LanguageUtils.getLocaleStringLog("geyser.commands.list.message", connector.getPlayers().size(), connector.getPlayers().values().stream().map(GeyserSession::getName).collect(Collectors.joining(" "))); + } + + sender.sendMessage(message); } } 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 2ddd61ed8a9..d8bf8583b03 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,12 +25,12 @@ package org.geysermc.connector.command.defaults; -import org.geysermc.connector.common.ChatColor; import org.geysermc.connector.common.PlatformType; 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.utils.LanguageUtils; public class ReloadCommand extends GeyserCommand { @@ -46,9 +46,18 @@ public void execute(CommandSender sender, String[] args) { if (!sender.isConsole() && connector.getPlatformType() == PlatformType.STANDALONE) { return; } - sender.sendMessage(ChatColor.YELLOW + "Reloading Geyser configurations... all connected bedrock clients will be kicked."); + + 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"); + } + + sender.sendMessage(message); + for (GeyserSession session : connector.getPlayers().values()) { - session.disconnect("Geyser has been reloaded... sorry for the inconvenience!"); + session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.commands.reload.kick", session.getClientData().getLanguageCode())); } connector.reload(); } diff --git a/connector/src/main/java/org/geysermc/connector/common/main/IGeyserMain.java b/connector/src/main/java/org/geysermc/connector/common/main/IGeyserMain.java index 906bd7865ef..a3e99ccb3b6 100644 --- a/connector/src/main/java/org/geysermc/connector/common/main/IGeyserMain.java +++ b/connector/src/main/java/org/geysermc/connector/common/main/IGeyserMain.java @@ -28,24 +28,39 @@ import javax.swing.*; import java.io.InputStream; +import java.lang.reflect.Method; +import java.util.Locale; import java.util.Scanner; public class IGeyserMain { + /** + * Displays the run help message in the console and a message box if running with a gui + */ public void displayMessage() { String message = createMessage(); - if (System.console() == null) { + if (System.console() == null && !isHeadless()) { JOptionPane.showMessageDialog(null, message, "GeyserMC Plugin: " + this.getPluginType(), JOptionPane.ERROR_MESSAGE); } printMessage(message); } + /** + * Load and format the run help text + * + * @return The formatted message + */ private String createMessage() { String message = ""; - InputStream helpStream = IGeyserMain.class.getClassLoader().getResourceAsStream("help.txt"); + InputStream helpStream = IGeyserMain.class.getClassLoader().getResourceAsStream("languages/run-help/" + Locale.getDefault().toString() + ".txt"); + + if (helpStream == null) { + helpStream = IGeyserMain.class.getClassLoader().getResourceAsStream("languages/run-help/en_US.txt"); + } + Scanner help = new Scanner(helpStream).useDelimiter("\\Z"); String line = ""; while (help.hasNext()) { @@ -60,14 +75,44 @@ private String createMessage() { return message; } + /** + * Check if we are in a headless environment + * + * @return Are we in a headless environment? + */ + private boolean isHeadless() { + try { + Class graphicsEnvironment = Class.forName("java.awt.GraphicsEnvironment"); + Method isHeadless = graphicsEnvironment.getDeclaredMethod("isHeadless"); + return (Boolean)isHeadless.invoke(null); + } catch (Exception ex) { } + + return true; + } + + /** + * Simply print a message to console + * + * @param message The message to print + */ private void printMessage(String message) { System.out.print(message); } + /** + * Get the platform the plugin is for + * + * @return The string representation of the plugin platforms name + */ public String getPluginType() { return "unknown"; } + /** + * Get the folder name the plugin should go into + * + * @return The string representation of the folder + */ public String getPluginFolder() { return "unknown"; } 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 5ea942c1a98..5727a902f46 100644 --- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java +++ b/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java @@ -28,6 +28,8 @@ import org.geysermc.connector.GeyserLogger; +import org.geysermc.connector.utils.LanguageUtils; + import java.nio.file.Path; import java.util.Map; @@ -111,9 +113,9 @@ interface IMetricsInfo { static void checkGeyserConfiguration(GeyserConfiguration geyserConfig, GeyserLogger geyserLogger) { if (geyserConfig.getConfigVersion() < CURRENT_CONFIG_VERSION) { - geyserLogger.warning("Your Geyser config is out of date! Please regenerate your config when possible."); + geyserLogger.warning(LanguageUtils.getLocaleStringLog("geyser.bootstrap.config.outdated")); } else if (geyserConfig.getConfigVersion() > CURRENT_CONFIG_VERSION) { - geyserLogger.warning("Your Geyser config is too new! Errors may occur."); + geyserLogger.warning(LanguageUtils.getLocaleStringLog("geyser.bootstrap.config.too_new")); } } } 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 27b7ad8fe0b..ad5cb42aa9b 100644 --- a/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java @@ -37,6 +37,7 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.ping.IGeyserPingPassthrough; import org.geysermc.connector.utils.MessageUtils; +import org.geysermc.connector.utils.LanguageUtils; import java.net.InetSocketAddress; @@ -50,13 +51,13 @@ public ConnectorServerEventHandler(GeyserConnector connector) { @Override public boolean onConnectionRequest(InetSocketAddress inetSocketAddress) { - connector.getLogger().info(inetSocketAddress + " tried to connect!"); + connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.attempt_connect", inetSocketAddress)); return true; } @Override public BedrockPong onQuery(InetSocketAddress inetSocketAddress) { - connector.getLogger().debug(inetSocketAddress + " has pinged you!"); + connector.getLogger().debug(LanguageUtils.getLocaleStringLog("geyser.network.pinged", inetSocketAddress)); GeyserConfiguration config = connector.getConfig(); @@ -108,7 +109,7 @@ public void onSessionCreation(BedrockServerSession bedrockServerSession) { bedrockServerSession.setLogging(true); bedrockServerSession.setPacketHandler(new UpstreamPacketHandler(connector, new GeyserSession(connector, bedrockServerSession))); bedrockServerSession.addDisconnectHandler(disconnectReason -> { - connector.getLogger().info("Bedrock user with ip: " + bedrockServerSession.getAddress().getAddress() + " has disconnected for reason " + disconnectReason); + connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.disconnect", bedrockServerSession.getAddress().getAddress(), disconnectReason)); GeyserSession player = connector.getPlayers().get(bedrockServerSession.getAddress()); if (player != null) { 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 79dcf138519..943b4568ca2 100644 --- a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java @@ -33,6 +33,7 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslatorRegistry; import org.geysermc.connector.utils.LoginEncryptionUtils; +import org.geysermc.connector.utils.LanguageUtils; public class UpstreamPacketHandler extends LoggingPacketHandler { @@ -47,10 +48,10 @@ private boolean translateAndDefault(BedrockPacket packet) { @Override public boolean handle(LoginPacket loginPacket) { if (loginPacket.getProtocolVersion() > GeyserConnector.BEDROCK_PACKET_CODEC.getProtocolVersion()) { - session.disconnect("Outdated Geyser proxy! I'm still on " + GeyserConnector.BEDROCK_PACKET_CODEC.getMinecraftVersion()); + session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.network.outdated.server", session.getClientData().getLanguageCode(), GeyserConnector.BEDROCK_PACKET_CODEC.getMinecraftVersion())); return true; } else if (loginPacket.getProtocolVersion() < GeyserConnector.BEDROCK_PACKET_CODEC.getProtocolVersion()) { - session.disconnect("Outdated Bedrock client! Please use " + GeyserConnector.BEDROCK_PACKET_CODEC.getMinecraftVersion()); + session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.network.outdated.client", session.getClientData().getLanguageCode(), GeyserConnector.BEDROCK_PACKET_CODEC.getMinecraftVersion())); return true; } @@ -70,7 +71,7 @@ public boolean handle(ResourcePackClientResponsePacket packet) { switch (packet.getStatus()) { case COMPLETED: session.connect(connector.getRemoteServer()); - connector.getLogger().info("Player connected with username " + session.getAuthData().getName()); + connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.connect", session.getAuthData().getName())); break; case HAVE_ALL_PACKS: ResourcePackStackPacket stack = new ResourcePackStackPacket(); @@ -97,7 +98,7 @@ private boolean couldLoginUserByName(String bedrockUsername) { GeyserConfiguration.IUserAuthenticationInfo info = connector.getConfig().getUserAuths().get(bedrockUsername); if (info != null) { - connector.getLogger().info("using stored credentials for bedrock user " + session.getAuthData().getName()); + connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.stored_credentials", session.getAuthData().getName())); session.authenticate(info.getEmail(), info.getPassword()); // TODO send a message to bedrock user telling them they are connected (if nothing like a motd @@ -111,6 +112,8 @@ private boolean couldLoginUserByName(String bedrockUsername) { @Override public boolean handle(SetLocalPlayerAsInitializedPacket packet) { + LanguageUtils.loadGeyserLocale(session.getClientData().getLanguageCode()); + 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) if (!couldLoginUserByName(session.getAuthData().getName())) { @@ -124,7 +127,7 @@ public boolean handle(SetLocalPlayerAsInitializedPacket packet) { @Override public boolean handle(MovePlayerPacket packet) { if (session.isLoggingIn()) { - session.sendMessage("Please wait until you are logged in..."); + session.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.auth.login.wait", session.getClientData().getLanguageCode())); } 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 93c619bfca3..666820cc290 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 @@ -258,12 +258,11 @@ public void fetchOurSkin(PlayerListPacket.Entry entry) { public void login() { if (connector.getAuthType() != AuthType.ONLINE) { - connector.getLogger().info( - "Attempting to login using " + connector.getAuthType().name().toLowerCase() + " mode... " + - (connector.getAuthType() == AuthType.OFFLINE ? - "authentication is disabled." : "authentication will be encrypted" - ) - ); + if (connector.getAuthType() == AuthType.OFFLINE) { + connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.login.offline")); + } else { + connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.login.floodgate")); + } authenticate(authData.getName()); } } @@ -274,7 +273,7 @@ public void authenticate(String username) { public void authenticate(String username, String password) { if (loggedIn) { - connector.getLogger().severe(username + " is already logged in!"); + connector.getLogger().severe(LanguageUtils.getLocaleStringLog("geyser.auth.already_loggedin", username)); return; } @@ -299,13 +298,13 @@ public void authenticate(String username, String password) { PublicKey.class ); } catch (IOException | InvalidKeySpecException | NoSuchAlgorithmException e) { - connector.getLogger().error("Error while reading Floodgate key file", e); + connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.bad_key"), e); } publicKey = key; } else publicKey = null; if (publicKey != null) { - connector.getLogger().info("Loaded Floodgate key!"); + connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.loaded_key")); } downstream = new Client(remoteServer.getAddress(), remoteServer.getPort(), protocol, new TcpSessionFactory()); @@ -326,7 +325,7 @@ public void packetSending(PacketSendingEvent event) { upstream.getSession().getAddress().getAddress().getHostAddress() )); } catch (Exception e) { - connector.getLogger().error("Failed to encrypt message", e); + connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.encrypt_fail"), e); } HandshakePacket handshakePacket = event.getPacket(); @@ -343,7 +342,7 @@ public void packetSending(PacketSendingEvent event) { public void connected(ConnectedEvent event) { loggingIn = false; loggedIn = true; - connector.getLogger().info(authData.getName() + " (logged in as: " + protocol.getProfile().getName() + ")" + " has connected to remote java server on address " + remoteServer.getAddress()); + connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.remote.connect", authData.getName(), protocol.getProfile().getName(), remoteServer.getAddress())); playerEntity.setUuid(protocol.getProfile().getId()); playerEntity.setUsername(protocol.getProfile().getName()); @@ -352,6 +351,7 @@ public void connected(ConnectedEvent event) { // Let the user know there locale may take some time to download // 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"); } @@ -363,7 +363,7 @@ public void connected(ConnectedEvent event) { public void disconnected(DisconnectedEvent event) { loggingIn = false; loggedIn = false; - connector.getLogger().info(authData.getName() + " has disconnected from remote java server on address " + remoteServer.getAddress() + " because of " + event.getReason()); + connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.remote.disconnect", authData.getName(), remoteServer.getAddress(), event.getReason())); if (event.getCause() != null) { event.getCause().printStackTrace(); } @@ -402,7 +402,7 @@ public void packetReceived(PacketReceivedEvent event) { @Override public void packetError(PacketErrorEvent event) { - connector.getLogger().warning("Downstream packet error! " + event.getCause().getMessage()); + connector.getLogger().warning(LanguageUtils.getLocaleStringLog("geyser.network.downstream_error", event.getCause().getMessage())); if (connector.getConfig().isDebugMode()) event.getCause().printStackTrace(); event.setSuppress(true); @@ -412,8 +412,8 @@ public void packetError(PacketErrorEvent event) { downstream.getSession().connect(); connector.addPlayer(this); } catch (InvalidCredentialsException | IllegalArgumentException e) { - connector.getLogger().info("User '" + username + "' entered invalid login info, kicking."); - disconnect("Invalid/incorrect login info"); + connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.login.invalid", username)); + disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.invalid.kick", getClientData().getLanguageCode())); } catch (RequestException ex) { ex.printStackTrace(); } @@ -442,7 +442,7 @@ public void disconnect(String reason) { } public void close() { - disconnect("Server closed."); + disconnect(LanguageUtils.getPlayerLocaleString("geyser.network.close", getClientData().getLanguageCode())); } public void setAuthenticationData(AuthData authData) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/BiomeTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/BiomeTranslator.java index 23a36641eb1..2daa6b2f4fd 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/BiomeTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/BiomeTranslator.java @@ -31,6 +31,7 @@ import com.nukkitx.nbt.NbtUtils; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.utils.FileUtils; +import org.geysermc.connector.utils.LanguageUtils; import java.io.InputStream; import java.util.Arrays; @@ -59,7 +60,7 @@ public static void init() { biomesTag = (NbtMap) biomenbtInputStream.readTag(); BIOMES = biomesTag; } catch (Exception ex) { - GeyserConnector.getInstance().getLogger().warning("Failed to get biomes from biome definitions, is there something wrong with the file?"); + GeyserConnector.getInstance().getLogger().warning(LanguageUtils.getLocaleStringLog("geyser.toolbox.fail.biome_read")); throw new AssertionError(ex); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/EntityIdentifierRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/EntityIdentifierRegistry.java index 59f4ae8ba1d..1f33666781d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/EntityIdentifierRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/EntityIdentifierRegistry.java @@ -30,6 +30,7 @@ import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtUtils; import org.geysermc.connector.utils.FileUtils; +import org.geysermc.connector.utils.LanguageUtils; import java.io.InputStream; @@ -54,7 +55,7 @@ public static void init() { try (NBTInputStream nbtInputStream = NbtUtils.createNetworkReader(stream)) { ENTITY_IDENTIFIERS = (NbtMap) nbtInputStream.readTag(); } catch (Exception e) { - throw new AssertionError("Unable to get entities from entity identifiers", e); + throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.toolbox.fail.entity"), e); } } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/PacketTranslatorRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/PacketTranslatorRegistry.java index a11dd40aff2..92d2e910294 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/PacketTranslatorRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/PacketTranslatorRegistry.java @@ -33,6 +33,7 @@ import it.unimi.dsi.fastutil.objects.ObjectArrayList; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.utils.LanguageUtils; import org.reflections.Reflections; import java.util.HashMap; @@ -66,10 +67,10 @@ public class PacketTranslatorRegistry { BEDROCK_TRANSLATOR.translators.put(targetPacket, translator); } else { - GeyserConnector.getInstance().getLogger().error("Class " + clazz.getCanonicalName() + " is annotated as a translator but has an invalid target packet."); + GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.network.translator.invalid_target", clazz.getCanonicalName())); } } catch (InstantiationException | IllegalAccessException e) { - GeyserConnector.getInstance().getLogger().error("Could not instantiate annotated translator " + clazz.getCanonicalName() + "."); + GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.network.translator.failed", clazz.getCanonicalName())); } } @@ -97,7 +98,7 @@ public

boolean translate(Class clazz, P packet, Geyse GeyserConnector.getInstance().getLogger().debug("Could not find packet for " + (packet.toString().length() > 25 ? packet.getClass().getSimpleName() : packet)); } } catch (Throwable ex) { - GeyserConnector.getInstance().getLogger().error("Could not translate packet " + packet.getClass().getSimpleName(), ex); + GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.network.translator.packet.failed", packet.getClass().getSimpleName()), ex); ex.printStackTrace(); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockPacketViolationWarningTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockPacketViolationWarningTranslator.java index 9e3c14e6dc3..5e4633ea550 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockPacketViolationWarningTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockPacketViolationWarningTranslator.java @@ -36,6 +36,7 @@ public class BedrockPacketViolationWarningTranslator extends PacketTranslator" + entry.getValue().asText()); + GeyserConnector.getInstance().getLogger().warning(LanguageUtils.getLocaleStringLog("geyser.particle.failed_map", entry.getKey(), entry.getValue().asText())); } } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/PlayerInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/PlayerInventoryTranslator.java index 41489ac2afc..7d7673c4ef2 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/PlayerInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/PlayerInventoryTranslator.java @@ -39,12 +39,12 @@ import org.geysermc.connector.network.translators.inventory.action.InventoryActionDataTranslator; import org.geysermc.connector.network.translators.item.ItemTranslator; import org.geysermc.connector.utils.InventoryUtils; +import org.geysermc.connector.utils.LanguageUtils; import java.util.List; public class PlayerInventoryTranslator extends InventoryTranslator { - private static final ItemData UNUSUABLE_CRAFTING_SPACE_BLOCK = InventoryUtils.createUnusableSpaceBlock( - "The creative crafting grid is\nunavailable in Java Edition"); + private static final ItemData UNUSUABLE_CRAFTING_SPACE_BLOCK = InventoryUtils.createUnusableSpaceBlock(LanguageUtils.getLocaleStringLog("geyser.inventory.unusable_item.creative")); public PlayerInventoryTranslator() { super(46); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/ChestInventoryUpdater.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/ChestInventoryUpdater.java index 7437a26e41e..e45945bc3ec 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/ChestInventoryUpdater.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/ChestInventoryUpdater.java @@ -34,11 +34,11 @@ import org.geysermc.connector.network.translators.inventory.InventoryTranslator; import org.geysermc.connector.network.translators.item.ItemTranslator; import org.geysermc.connector.utils.InventoryUtils; +import org.geysermc.connector.utils.LanguageUtils; @AllArgsConstructor public class ChestInventoryUpdater extends InventoryUpdater { - private static final ItemData UNUSUABLE_SPACE_BLOCK = InventoryUtils.createUnusableSpaceBlock( - "This slot does not exist in the inventory\non Java Edition, as there is less\nrows than possible in Bedrock"); + private static final ItemData UNUSUABLE_SPACE_BLOCK = InventoryUtils.createUnusableSpaceBlock(LanguageUtils.getLocaleStringLog("geyser.inventory.unusable_item.slot")); private final int paddedSize; 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 0979e5731b1..23c566d7c9b 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 @@ -37,16 +37,12 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.utils.FileUtils; +import org.geysermc.connector.utils.LanguageUtils; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; -import java.util.ArrayList; -import java.util.Base64; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; +import java.util.*; /** * Registry for anything item related. @@ -82,7 +78,7 @@ public static void init() { try { itemEntries = GeyserConnector.JSON_MAPPER.readValue(stream, itemEntriesType); } catch (Exception e) { - throw new AssertionError("Unable to load Bedrock runtime item IDs", e); + throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.toolbox.fail.runtime_bedrock"), e); } for (JsonNode entry : itemEntries) { @@ -95,7 +91,7 @@ public static void init() { try { items = GeyserConnector.JSON_MAPPER.readTree(stream); } catch (Exception e) { - throw new AssertionError("Unable to load Java runtime item IDs", e); + throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.toolbox.fail.runtime_java"), e); } int itemIndex = 0; @@ -144,7 +140,7 @@ public static void init() { try { creativeItemEntries = GeyserConnector.JSON_MAPPER.readTree(stream).get("items"); } catch (Exception e) { - throw new AssertionError("Unable to load creative items", e); + throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.toolbox.fail.creative"), e); } List creativeItems = new ArrayList<>(); 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 6811d6bb6f3..7811d9c0ae2 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 @@ -50,6 +50,7 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.ItemRemapper; import org.geysermc.connector.utils.MessageUtils; +import org.geysermc.connector.utils.LanguageUtils; import org.reflections.Reflections; import java.util.*; @@ -88,14 +89,13 @@ public static void init() { for (ItemEntry item : appliedItems) { ItemTranslator registered = ITEM_STACK_TRANSLATORS.get(item.getJavaId()); if (registered != null) { - GeyserConnector.getInstance().getLogger().error("Could not instantiate annotated item translator " + clazz.getCanonicalName() + "." + - " Item translator " + registered.getClass().getCanonicalName() + " is already registered for the item " + item.getJavaIdentifier()); + GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.network.translator.item.already_registered", clazz.getCanonicalName(), registered.getClass().getCanonicalName(), item.getJavaIdentifier())); continue; } ITEM_STACK_TRANSLATORS.put(item.getJavaId(), itemStackTranslator); } } catch (InstantiationException | IllegalAccessException e) { - GeyserConnector.getInstance().getLogger().error("Could not instantiate annotated item translator " + clazz.getCanonicalName() + "."); + GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.network.translator.item.failed", clazz.getCanonicalName())); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerPositionRotationTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerPositionRotationTranslator.java index 8b0b8201487..49177912155 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerPositionRotationTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerPositionRotationTranslator.java @@ -41,6 +41,7 @@ import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.utils.ChunkUtils; +import org.geysermc.connector.utils.LanguageUtils; @Translator(packet = ServerPlayerPositionRotationPacket.class) public class JavaPlayerPositionRotationTranslator extends PacketTranslator { @@ -90,7 +91,7 @@ public void translate(ServerPlayerPositionRotationPacket packet, GeyserSession s ChunkUtils.updateChunkPosition(session, pos.toInt()); - session.getConnector().getLogger().info("Spawned player at " + packet.getX() + " " + packet.getY() + " " + packet.getZ()); + session.getConnector().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.entity.player.spawn", packet.getX(), packet.getY(), packet.getZ())); return; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnEntityTranslator.java index 742fa97e937..d9c859d3e77 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnEntityTranslator.java @@ -36,6 +36,7 @@ import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.utils.EntityUtils; +import org.geysermc.connector.utils.LanguageUtils; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; @@ -52,7 +53,7 @@ public void translate(ServerSpawnEntityPacket packet, GeyserSession session) { org.geysermc.connector.entity.type.EntityType type = EntityUtils.toBedrockEntity(packet.getType()); if (type == null) { - session.getConnector().getLogger().warning("Entity type " + packet.getType() + " was null."); + session.getConnector().getLogger().warning(LanguageUtils.getLocaleStringLog("geyser.entity.type_null", packet.getType())); return; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnLivingEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnLivingEntityTranslator.java index 8c246e5148f..5e33c8a15e4 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnLivingEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnLivingEntityTranslator.java @@ -33,6 +33,7 @@ import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.utils.EntityUtils; +import org.geysermc.connector.utils.LanguageUtils; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; @@ -48,7 +49,7 @@ public void translate(ServerSpawnLivingEntityPacket packet, GeyserSession sessio EntityType type = EntityUtils.toBedrockEntity(packet.getType()); if (type == null) { - session.getConnector().getLogger().warning("Entity type " + packet.getType() + " was null."); + session.getConnector().getLogger().warning(LanguageUtils.getLocaleStringLog("geyser.entity.type_null", packet.getType())); return; } 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 e01b95e9094..130a5f653be 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 @@ -32,6 +32,7 @@ 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.LanguageUtils; import org.geysermc.connector.utils.SkinUtils; @Translator(packet = ServerSpawnPlayerPacket.class) @@ -44,7 +45,7 @@ public void translate(ServerSpawnPlayerPacket packet, GeyserSession session) { PlayerEntity entity = session.getEntityCache().getPlayerEntity(packet.getUuid()); if (entity == null) { - GeyserConnector.getInstance().getLogger().error("Haven't received PlayerListEntry packet before spawning player! We ignore the player " + packet.getUuid()); + GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.entity.player.failed_list", packet.getUuid())); return; } 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 d3bc6b4e77f..f6ae4d6feef 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 @@ -25,9 +25,8 @@ package org.geysermc.connector.network.translators.java.scoreboard; -import java.util.Arrays; -import java.util.Set; - +import com.github.steveice10.mc.protocol.packet.ingame.server.scoreboard.ServerTeamPacket; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; @@ -35,11 +34,11 @@ import org.geysermc.connector.scoreboard.Scoreboard; import org.geysermc.connector.scoreboard.Team; import org.geysermc.connector.scoreboard.UpdateType; +import org.geysermc.connector.utils.LanguageUtils; import org.geysermc.connector.utils.MessageUtils; -import com.github.steveice10.mc.protocol.packet.ingame.server.scoreboard.ServerTeamPacket; - -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import java.util.Arrays; +import java.util.Set; @Translator(packet = ServerTeamPacket.class) public class JavaTeamTranslator extends PacketTranslator { @@ -66,21 +65,21 @@ public void translate(ServerTeamPacket packet, GeyserSession session) { .setSuffix(MessageUtils.getBedrockMessage(packet.getSuffix())) .setUpdateType(UpdateType.UPDATE); } else { - GeyserConnector.getInstance().getLogger().error("Error while translating Team Packet " + packet.getAction() + "! Scoreboard Team " + packet.getTeamName() + " is not registered."); + GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.network.translator.team.failed_not_registered", packet.getAction(), packet.getTeamName())); } break; case ADD_PLAYER: if(team != null){ team.addEntities(packet.getPlayers()); } else { - GeyserConnector.getInstance().getLogger().error("Error while translating Team Packet " + packet.getAction() + "! Scoreboard Team " + packet.getTeamName() + " is not registered."); + GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.network.translator.team.failed_not_registered", packet.getAction(), packet.getTeamName())); } break; case REMOVE_PLAYER: if(team != null){ team.removeEntities(packet.getPlayers()); } else { - GeyserConnector.getInstance().getLogger().error("Error while translating Team Packet " + packet.getAction() + "! Scoreboard Team " + packet.getTeamName() + " is not registered."); + GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.network.translator.team.failed_not_registered", packet.getAction(), packet.getTeamName())); } break; case REMOVE: diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaUpdateScoreTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaUpdateScoreTranslator.java index eac2ed049b6..827e4c7f4b9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaUpdateScoreTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaUpdateScoreTranslator.java @@ -34,6 +34,7 @@ import com.github.steveice10.mc.protocol.data.game.scoreboard.ScoreboardAction; import com.github.steveice10.mc.protocol.packet.ingame.server.scoreboard.ServerUpdateScorePacket; +import org.geysermc.connector.utils.LanguageUtils; @Translator(packet = ServerUpdateScorePacket.class) public class JavaUpdateScoreTranslator extends PacketTranslator { @@ -45,7 +46,7 @@ public void translate(ServerUpdateScorePacket packet, GeyserSession session) { Objective objective = scoreboard.getObjective(packet.getObjective()); if (objective == null && packet.getAction() != ScoreboardAction.REMOVE) { - GeyserConnector.getInstance().getLogger().info("Tried to update score without the existence of its requested objective '" + packet.getObjective() + '\''); + GeyserConnector.getInstance().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.translator.score.failed_objective", packet.getObjective())); return; } 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 557f37a339c..c4401c4c854 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 @@ -34,6 +34,7 @@ import it.unimi.dsi.fastutil.objects.ObjectArrayList; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.utils.BlockEntityUtils; +import org.geysermc.connector.utils.LanguageUtils; import org.reflections.Reflections; import java.util.HashMap; @@ -72,7 +73,7 @@ public static void init() { try { BLOCK_ENTITY_TRANSLATORS.put(clazz.getAnnotation(BlockEntity.class).name(), (BlockEntityTranslator) clazz.newInstance()); } catch (InstantiationException | IllegalAccessException e) { - GeyserConnector.getInstance().getLogger().error("Could not instantiate annotated block entity " + clazz.getCanonicalName() + "."); + GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.network.translator.block_entity.failed", clazz.getCanonicalName())); } } for (Class clazz : ref.getSubTypesOf(RequiresBlockState.class)) { @@ -81,7 +82,7 @@ public static void init() { try { REQUIRES_BLOCK_STATE_LIST.add((RequiresBlockState) clazz.newInstance()); } catch (InstantiationException | IllegalAccessException e) { - GeyserConnector.getInstance().getLogger().error("Could not instantiate required block state " + clazz.getCanonicalName() + "."); + GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.network.translator.block_state.failed", clazz.getCanonicalName())); } } } 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 59d9b25f64b..5fdda617ff1 100644 --- a/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java +++ b/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java @@ -34,6 +34,7 @@ import lombok.Getter; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.utils.LanguageUtils; import java.util.*; import java.util.concurrent.atomic.AtomicLong; @@ -78,7 +79,7 @@ public Objective registerNewObjective(String objectiveId, ScoreboardPosition dis public Team registerNewTeam(String teamName, Set players) { if (teams.containsKey(teamName)) { - session.getConnector().getLogger().info("Ignoring team " + teamName + ". It overrides without removing old team."); + session.getConnector().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.translator.team.failed_overrides", teamName)); return getTeam(teamName); } diff --git a/connector/src/main/java/org/geysermc/connector/utils/DockerCheck.java b/connector/src/main/java/org/geysermc/connector/utils/DockerCheck.java index 09c78da92c2..a6eb6c9ba9a 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/DockerCheck.java +++ b/connector/src/main/java/org/geysermc/connector/utils/DockerCheck.java @@ -49,8 +49,8 @@ public static void check(GeyserBootstrap bootstrap) { String output = new String(Files.readAllBytes(Paths.get("/proc/1/cgroup"))); if (output.contains("docker")) { - bootstrap.getGeyserLogger().warning("You are most likely in a Docker container, this may cause connection issues from Geyser to the Java server"); - bootstrap.getGeyserLogger().warning("We recommended using the following IP as the remote address: " + ipAddress); + bootstrap.getGeyserLogger().warning(LanguageUtils.getLocaleStringLog("geyser.bootstrap.docker_warn.line1")); + bootstrap.getGeyserLogger().warning(LanguageUtils.getLocaleStringLog("geyser.bootstrap.docker_warn.line2", ipAddress)); } } } catch (Exception e) { } // Ignore any errors, inc ip failed to fetch, process could not run or access denied 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 04b6ecc4d98..0b7b5c5cf09 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java @@ -135,7 +135,7 @@ public static void writeFile(String name, char[] data) throws IOException { public static InputStream getResource(String resource) { InputStream stream = FileUtils.class.getClassLoader().getResourceAsStream(resource); if (stream == null) { - throw new AssertionError("Unable to find resource: " + resource); + throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.toolbox.fail.resource", resource)); } return stream; } diff --git a/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java b/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java index 9862159dc34..325710c6d6a 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java @@ -140,7 +140,8 @@ public static ItemData createUnusableSpaceBlock(String description) { NbtMapBuilder root = NbtMap.builder(); NbtMapBuilder display = NbtMap.builder(); - display.putString("Name", ChatColor.RESET + "Unusable inventory space"); + // Not ideal to use log here but we dont get a session + display.putString("Name", ChatColor.RESET + LanguageUtils.getLocaleStringLog("geyser.inventory.unusable_item.name")); display.putList("Lore", NbtType.STRING, Collections.singletonList(ChatColor.RESET + ChatColor.DARK_PURPLE + description)); root.put("display", display.build()); diff --git a/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java new file mode 100644 index 00000000000..6d353adbe14 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java @@ -0,0 +1,207 @@ +/* + * 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 org.geysermc.connector.GeyserConnector; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.text.MessageFormat; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; + +public class LanguageUtils { + + /** + * If we determine the locale that the user wishes to use, use that locale + */ + private static String CACHED_LOCALE; + + private static final Map LOCALE_MAPPINGS = new HashMap<>(); + + static { + // Load it as a backup in case something goes really wrong + if (!"en_US".equals(formatLocale(getDefaultLocale()))) { // getDefaultLocale() loads the locale automatically + loadGeyserLocale("en_US"); + } + } + + /** + * Loads a Geyser locale from resources, if the file doesn't exist it just logs a warning + * + * @param locale Locale to load + */ + public static void loadGeyserLocale(String locale) { + locale = formatLocale(locale); + + InputStream localeStream = GeyserConnector.class.getClassLoader().getResourceAsStream("languages/texts/" + locale + ".properties"); + + // Load the locale + if (localeStream != null) { + Properties localeProp = new Properties(); + try { + localeProp.load(new InputStreamReader(localeStream, StandardCharsets.UTF_8)); + } catch (Exception e) { + throw new AssertionError(getLocaleStringLog("geyser.language.load_failed", locale), e); + } + + // Insert the locale into the mappings + LOCALE_MAPPINGS.put(locale, localeProp); + } else { + if (!locale.toLowerCase().equals(getDefaultLocale().toLowerCase())) { // The default locale was invalid fallback to en_us + GeyserConnector.getInstance().getLogger().warning(getLocaleStringLog("geyser.language.missing_file", locale)); + } + } + } + + /** + * Get a formatted language string with the default locale for Geyser + * + * @param key Language string to translate + * @param values Values to put into the string + * @return Translated string or the original message if it was not found in the given locale + */ + public static String getLocaleStringLog(String key, Object... values) { + return getPlayerLocaleString(key, getDefaultLocale(), values); + } + + /** + * Get a formatted language string with the given locale for Geyser + * + * @param key Language string to translate + * @param locale Locale to translate to + * @param values Values to put into the string + * @return Translated string or the original message if it was not found in the given locale + */ + public static String getPlayerLocaleString(String key, String locale, Object... values) { + locale = formatLocale(locale); + + Properties properties = LOCALE_MAPPINGS.get(locale); + String formatString = properties.getProperty(key); + + // Try and get the key from the default locale + if (formatString == null) { + properties = LOCALE_MAPPINGS.get(formatLocale(getDefaultLocale())); + formatString = properties.getProperty(key); + } + + // Try and get the key from en_US (this should only ever happen in development) + if (formatString == null) { + properties = LOCALE_MAPPINGS.get("en_US"); + formatString = properties.getProperty(key); + } + + // Final fallback + if (formatString == null) { + formatString = key; + } + + return MessageFormat.format(formatString.replace("&", "\u00a7"), values); + } + + /** + * Cleans up and formats a locale string + * + * @param locale The locale to format + * @return The formatted locale + */ + private static String formatLocale(String locale) { + try { + String[] parts = locale.toLowerCase().split("_"); + String newLocale = parts[0] + "_" + parts[1].toUpperCase(); + switch (newLocale) { // Fallback to the closest language if we don't support it but Bedrock does. + case "es_MX": + return "es_ES"; + case "pt_BR": + return "pt_PT"; + case "fr_CA": + return "fr_FR"; + default: + return newLocale; + } + } catch (Exception e) { + return locale; + } + } + + /** + * Get the default locale that Geyser should use + * @return the current default locale + */ + public static String getDefaultLocale() { + if (CACHED_LOCALE != null) return CACHED_LOCALE; // We definitely know the locale the user is using + String locale; + boolean isValid = true; + if (GeyserConnector.getInstance() != null && + GeyserConnector.getInstance().getConfig() != null && + GeyserConnector.getInstance().getConfig().getDefaultLocale() != null) { // If the config option for getDefaultLocale does not equal null, use that + locale = formatLocale(GeyserConnector.getInstance().getConfig().getDefaultLocale()); + if (isValidLanguage(locale)) { + CACHED_LOCALE = locale; + return locale; + } else { + isValid = false; + } + } + locale = formatLocale(Locale.getDefault().getLanguage() + "_" + Locale.getDefault().getCountry()); + if (!isValidLanguage(locale)) { // Bedrock does not support this language + locale = "en_US"; + } + if (GeyserConnector.getInstance() != null && + GeyserConnector.getInstance().getConfig() != null && (GeyserConnector.getInstance().getConfig().getDefaultLocale() == null || !isValid)) { // Means we should use the system locale for sure + CACHED_LOCALE = locale; + } + return locale; + } + + /** + * Ensures that the given locale is supported by Bedrock + * @param locale the locale to validate + * @return true if the given locale is supported by Bedrock and by extension Geyser + */ + private static boolean isValidLanguage(String locale) { + boolean result = true; + if (FileUtils.class.getResource("/languages/texts/" + locale + ".properties") == null) { + result = false; + if (GeyserConnector.getInstance() != null && GeyserConnector.getInstance().getLogger() != null) { // Could be too early for these to be initialized + GeyserConnector.getInstance().getLogger().warning(locale + " is not a valid Bedrock language."); // We can't translate this since we just loaded an invalid language + } + } else { + if (!LOCALE_MAPPINGS.containsKey(locale)) { + loadGeyserLocale(locale); + } + } + return result; + } + + public static void init() { + // no-op + } +} 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 78752591359..285846a9733 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java @@ -48,8 +48,6 @@ public class LocaleUtils { private static final Map ASSET_MAP = new HashMap<>(); - private static final String DEFAULT_LOCALE = (GeyserConnector.getInstance().getConfig().getDefaultLocale() != null ? GeyserConnector.getInstance().getConfig().getDefaultLocale() : "en_us"); - private static String smallestURL = ""; static { @@ -60,7 +58,7 @@ public class LocaleUtils { // Download the latest asset list and cache it generateAssetCache(); - downloadAndLoadLocale(DEFAULT_LOCALE); + downloadAndLoadLocale(LanguageUtils.getDefaultLocale()); } /** @@ -82,7 +80,7 @@ private static void generateAssetCache() { // Make sure we definitely got a version if (latestInfoURL.isEmpty()) { - throw new Exception("Unable to get latest Minecraft version"); + throw new Exception(LanguageUtils.getLocaleStringLog("geyser.locale.fail.latest_version")); } // Get the individual version manifest @@ -105,7 +103,7 @@ private static void generateAssetCache() { ASSET_MAP.put(entry.getKey(), asset); } } catch (Exception e) { - GeyserConnector.getInstance().getLogger().info("Failed to load locale asset cache: " + (!e.getMessage().isEmpty() ? e.getMessage() : e.getStackTrace())); + GeyserConnector.getInstance().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.locale.fail.asset_cache", (!e.getMessage().isEmpty() ? e.getMessage() : e.getStackTrace()))); } } @@ -119,7 +117,7 @@ public static void downloadAndLoadLocale(String locale) { // Check the locale isn't already loaded if (!ASSET_MAP.containsKey("minecraft/lang/" + locale + ".json") && !locale.equals("en_us")) { - GeyserConnector.getInstance().getLogger().warning("Invalid locale requested to download and load: " + locale); + GeyserConnector.getInstance().getLogger().warning(LanguageUtils.getLocaleStringLog("geyser.locale.fail.invalid", locale)); return; } @@ -170,7 +168,7 @@ private static void loadLocale(String locale) { try { localeStream = new FileInputStream(localeFile); } catch (FileNotFoundException e) { - throw new AssertionError("Unable to load locale: " + locale + " (" + e.getMessage() + ")"); + throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.locale.fail.file", locale, e.getMessage())); } // Parse the file as json @@ -178,7 +176,7 @@ private static void loadLocale(String locale) { try { localeObj = GeyserConnector.JSON_MAPPER.readTree(localeStream); } catch (Exception e) { - throw new AssertionError("Unable to load Java edition lang map for " + locale, e); + throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.locale.fail.json", locale), e); } // Parse all the locale fields @@ -192,7 +190,7 @@ private static void loadLocale(String locale) { // Insert the locale into the mappings LOCALE_MAPPINGS.put(locale.toLowerCase(), langMap); } else { - GeyserConnector.getInstance().getLogger().warning("Missing locale file: " + locale); + GeyserConnector.getInstance().getLogger().warning(LanguageUtils.getLocaleStringLog("geyser.locale.fail.missing", locale)); } } @@ -204,7 +202,7 @@ private static void loadLocale(String locale) { private static void downloadEN_US(File localeFile) { try { // Let the user know we are downloading the JAR - GeyserConnector.getInstance().getLogger().info("Downloading Minecraft JAR to extract en_us locale, please wait... (this may take some time depending on the speed of your internet connection)"); + GeyserConnector.getInstance().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.locale.download.en_us")); GeyserConnector.getInstance().getLogger().debug("Download URL: " + smallestURL); // Download the smallest JAR (client or server) @@ -233,7 +231,7 @@ private static void downloadEN_US(File localeFile) { // Delete the nolonger needed client/server jar Files.delete(tmpFilePath); } catch (Exception e) { - throw new AssertionError("Unable to download and extract en_us locale!", e); + throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.locale.fail.en_us"), e); } } @@ -247,7 +245,7 @@ private static void downloadEN_US(File localeFile) { public static String getLocaleString(String messageText, String locale) { Map localeStrings = LocaleUtils.LOCALE_MAPPINGS.get(locale.toLowerCase()); if (localeStrings == null) - localeStrings = LocaleUtils.LOCALE_MAPPINGS.get(DEFAULT_LOCALE); + localeStrings = LocaleUtils.LOCALE_MAPPINGS.get(LanguageUtils.getDefaultLocale()); return localeStrings.getOrDefault(messageText, messageText); } 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 f9a7fec264e..3d4dd506a9b 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java @@ -156,18 +156,20 @@ private static void startEncryptionHandshake(GeyserSession session, PublicKey ke private static int AUTH_DETAILS_FORM_ID = 1337; public static void showLoginWindow(GeyserSession session) { - SimpleFormWindow window = new SimpleFormWindow("Login", "You need a Java Edition account to play on this server."); - window.getButtons().add(new FormButton("Login with Minecraft")); - window.getButtons().add(new FormButton("Disconnect")); + String userLanguage = session.getClientData().getLanguageCode(); + 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))); session.sendForm(window, AUTH_FORM_ID); } public static void showLoginDetailsWindow(GeyserSession session) { - CustomFormWindow window = new CustomFormBuilder("Login Details") - .addComponent(new LabelComponent("Enter the credentials for your Minecraft: Java Edition account below.")) - .addComponent(new InputComponent("Email/Username", "account@geysermc.org", "")) - .addComponent(new InputComponent("Password", "123456", "")) + String userLanguage = session.getClientData().getLanguageCode(); + 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", "")) + .addComponent(new InputComponent(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.details.pass", userLanguage), "123456", "")) .build(); session.sendForm(window, AUTH_DETAILS_FORM_ID); @@ -203,8 +205,8 @@ public static boolean authenticateFromForm(GeyserSession session, GeyserConnecto if (response != null) { if (response.getClickedButtonId() == 0) { showLoginDetailsWindow(session); - } else if (response.getClickedButtonId() == 1) { - session.disconnect("Login is required"); + } else if(response.getClickedButtonId() == 1) { + session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.getClientData().getLanguageCode())); } } else { showLoginWindow(session); 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 9e071247769..225885769a5 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java @@ -246,7 +246,7 @@ public static void requestAndHandleSkinAndCape(PlayerEntity entity, GeyserSessio } } } catch (Exception e) { - GeyserConnector.getInstance().getLogger().error("Failed getting skin for " + entity.getUuid(), e); + GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.skin.fail", entity.getUuid()), e); } if (skinAndCapeConsumer != null) skinAndCapeConsumer.accept(skinAndCape); @@ -257,7 +257,7 @@ public static void requestAndHandleSkinAndCape(PlayerEntity entity, GeyserSessio public static void handleBedrockSkin(PlayerEntity playerEntity, BedrockClientData clientData) { GameProfileData data = GameProfileData.from(playerEntity.getProfile()); - GeyserConnector.getInstance().getLogger().info("Registering bedrock skin for " + playerEntity.getUsername() + " (" + playerEntity.getUuid() + ")"); + GeyserConnector.getInstance().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.skin.bedrock.register", playerEntity.getUsername(), playerEntity.getUuid())); try { byte[] skinBytes = com.github.steveice10.mc.auth.util.Base64.decode(clientData.getSkinData().getBytes("UTF-8")); @@ -270,7 +270,7 @@ public static void handleBedrockSkin(PlayerEntity playerEntity, BedrockClientDat SkinProvider.storeBedrockSkin(playerEntity.getUuid(), data.getSkinUrl(), skinBytes); SkinProvider.storeBedrockGeometry(playerEntity.getUuid(), geometryNameBytes, geometryBytes); } else { - GeyserConnector.getInstance().getLogger().info("Unable to load bedrock skin for '" + playerEntity.getUsername() + "' as they are likely using a customised skin"); + GeyserConnector.getInstance().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.skin.bedrock.fail", playerEntity.getUsername())); GeyserConnector.getInstance().getLogger().debug("The size of '" + playerEntity.getUsername() + "' skin is: " + clientData.getSkinImageWidth() + "x" + clientData.getSkinImageHeight()); } diff --git a/connector/src/main/resources/config.yml b/connector/src/main/resources/config.yml index eb4c1ddcafc..91f95e6edc6 100644 --- a/connector/src/main/resources/config.yml +++ b/connector/src/main/resources/config.yml @@ -78,8 +78,8 @@ allow-third-party-ears: false # Allow a fake cooldown indicator to be sent. Bedrock players do not see a cooldown as they still use 1.8 combat show-cooldown: true -# The default locale if we dont have the one the client requested -default-locale: en_us +# The default locale if we dont have the one the client requested. Uncomment to not use the default system language. +# default-locale: en_us # Configures if chunk caching should be enabled or not. This keeps an individual # record of each block the client loads in. While this feature does allow for a few diff --git a/connector/src/main/resources/languages b/connector/src/main/resources/languages new file mode 160000 index 00000000000..08be7fdd7bd --- /dev/null +++ b/connector/src/main/resources/languages @@ -0,0 +1 @@ +Subproject commit 08be7fdd7bd3c1ade46fa8968c04d3d67bb0d378 From 6cdf1eaf439b4936bbf0fe490cd08daf934d340b Mon Sep 17 00:00:00 2001 From: rtm516 Date: Mon, 6 Jul 2020 00:46:51 +0100 Subject: [PATCH 40/72] Fix player table throwing errors on interaction --- .../standalone/gui/GeyserStandaloneGUI.java | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) 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 5a6b4c89da0..9be0e1c6a1f 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 @@ -47,20 +47,19 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Vector; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class GeyserStandaloneGUI { - private static final String[] playerTableHeadings = new String[] { - LanguageUtils.getLocaleStringLog("geyser.gui.table.ip"), - LanguageUtils.getLocaleStringLog("geyser.gui.table.username")}; + private static final DefaultTableModel playerTableModel = new DefaultTableModel(); private static final List ramValues = new ArrayList<>(); private static final ColorPane consolePane = new ColorPane(); private static final GraphPanel ramGraph = new GraphPanel(); - private static final JTable playerTable = new JTable(new String[][] { }, playerTableHeadings); + private static final JTable playerTable = new JTable(playerTableModel); private static final int originalFontSize = consolePane.getFont().getSize(); private static final long MEGABYTE = 1024L * 1024L; @@ -201,6 +200,9 @@ public void componentResized(ComponentEvent e) { ramGraph.setXLabel(LanguageUtils.getLocaleStringLog("geyser.gui.graph.loading")); rightContentPane.add(ramGraph); + playerTableModel.addColumn(LanguageUtils.getLocaleStringLog("geyser.gui.table.ip")); + playerTableModel.addColumn(LanguageUtils.getLocaleStringLog("geyser.gui.table.username")); + JScrollPane playerScrollPane = new JScrollPane(playerTable); rightContentPane.add(playerScrollPane); @@ -288,18 +290,17 @@ public void startUpdateThread() { Runnable periodicTask = () -> { if (GeyserConnector.getInstance() != null) { // Update player table - String[][] playerNames = new String[GeyserConnector.getInstance().getPlayers().size()][2]; - int i = 0; + playerTableModel.getDataVector().removeAllElements(); + for (Map.Entry player : GeyserConnector.getInstance().getPlayers().entrySet()) { - playerNames[i][0] = player.getKey().getHostName(); - playerNames[i][1] = player.getValue().getPlayerEntity().getUsername(); + Vector row = new Vector(); + row.add(player.getKey().getHostName()); + row.add(player.getValue().getPlayerEntity().getUsername()); - i++; + playerTableModel.addRow(row); } - DefaultTableModel model = new DefaultTableModel(playerNames, playerTableHeadings); - playerTable.setModel(model); - model.fireTableDataChanged(); + playerTableModel.fireTableDataChanged(); } // Update ram graph From ca4d827d2868f8075d4375dd76acbae5cfa2ea87 Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Sun, 5 Jul 2020 21:13:28 -0400 Subject: [PATCH 41/72] Don't cause a recursion error if Geyser can't find the locale --- .../main/java/org/geysermc/connector/utils/LanguageUtils.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 6d353adbe14..6172cc9e8b0 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java @@ -75,8 +75,8 @@ public static void loadGeyserLocale(String locale) { // Insert the locale into the mappings LOCALE_MAPPINGS.put(locale, localeProp); } else { - if (!locale.toLowerCase().equals(getDefaultLocale().toLowerCase())) { // The default locale was invalid fallback to en_us - GeyserConnector.getInstance().getLogger().warning(getLocaleStringLog("geyser.language.missing_file", locale)); + if (GeyserConnector.getInstance() != null && GeyserConnector.getInstance().getLogger() != null) { + GeyserConnector.getInstance().getLogger().warning("Missing locale: " + locale); } } } From afcf1e3acdad3801d08b121408819f0719030791 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Sun, 5 Jul 2020 21:38:24 -0400 Subject: [PATCH 42/72] Change versioning to match supported Bedrock version; add versioning command (#730) * Change versioning to match supported Bedrock version Line up Geyser's versioning to match with the highest/currently supported Bedrock version for future tracking of older Geyser versions. * Add version command * Fix DEV check for version command * Remove SNAPSHOT * Update languages submodule Co-authored-by: rtm516 --- bootstrap/bungeecord/pom.xml | 4 +- bootstrap/pom.xml | 2 +- bootstrap/spigot/pom.xml | 4 +- bootstrap/sponge/pom.xml | 4 +- bootstrap/standalone/pom.xml | 4 +- bootstrap/velocity/pom.xml | 4 +- common/pom.xml | 2 +- connector/pom.xml | 4 +- .../connector/command/CommandManager.java | 1 + .../command/defaults/VersionCommand.java | 76 +++++++++++++++++++ connector/src/main/resources/languages | 2 +- 11 files changed, 92 insertions(+), 15 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/command/defaults/VersionCommand.java diff --git a/bootstrap/bungeecord/pom.xml b/bootstrap/bungeecord/pom.xml index 875991fa0e6..565264f0f49 100644 --- a/bootstrap/bungeecord/pom.xml +++ b/bootstrap/bungeecord/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 1.0-SNAPSHOT + 1.0.0 ../ bootstrap-bungeecord @@ -14,7 +14,7 @@ org.geysermc connector - 1.0-SNAPSHOT + 1.0.0 compile diff --git a/bootstrap/pom.xml b/bootstrap/pom.xml index 87302d4d82c..85ede3466d9 100644 --- a/bootstrap/pom.xml +++ b/bootstrap/pom.xml @@ -10,7 +10,7 @@ ../ bootstrap-parent - 1.0-SNAPSHOT + 1.0.0 pom diff --git a/bootstrap/spigot/pom.xml b/bootstrap/spigot/pom.xml index 6439eb23326..e05ad7f03d9 100644 --- a/bootstrap/spigot/pom.xml +++ b/bootstrap/spigot/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 1.0-SNAPSHOT + 1.0.0 ../ bootstrap-spigot @@ -14,7 +14,7 @@ org.geysermc connector - 1.0-SNAPSHOT + 1.0.0 compile diff --git a/bootstrap/sponge/pom.xml b/bootstrap/sponge/pom.xml index 4a995711a83..cca3fcaae78 100644 --- a/bootstrap/sponge/pom.xml +++ b/bootstrap/sponge/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 1.0-SNAPSHOT + 1.0.0 ../ bootstrap-sponge @@ -14,7 +14,7 @@ org.geysermc connector - 1.0-SNAPSHOT + 1.0.0 compile diff --git a/bootstrap/standalone/pom.xml b/bootstrap/standalone/pom.xml index 60b0ba81f25..468042b8ffa 100644 --- a/bootstrap/standalone/pom.xml +++ b/bootstrap/standalone/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 1.0-SNAPSHOT + 1.0.0 ../ bootstrap-standalone @@ -14,7 +14,7 @@ org.geysermc connector - 1.0-SNAPSHOT + 1.0.0 compile diff --git a/bootstrap/velocity/pom.xml b/bootstrap/velocity/pom.xml index 78f21973138..7c42ba3362f 100644 --- a/bootstrap/velocity/pom.xml +++ b/bootstrap/velocity/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 1.0-SNAPSHOT + 1.0.0 ../ bootstrap-velocity @@ -14,7 +14,7 @@ org.geysermc connector - 1.0-SNAPSHOT + 1.0.0 compile diff --git a/common/pom.xml b/common/pom.xml index 0a47fbcaab8..0df8ef4bf6c 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -10,7 +10,7 @@ ../ common - 1.0-SNAPSHOT + 1.0.0 com.google.code.gson diff --git a/connector/pom.xml b/connector/pom.xml index 70ad0e26db6..b2bc15263cc 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -10,12 +10,12 @@ ../ connector - 1.0-SNAPSHOT + 1.0.0 org.geysermc common - 1.0-SNAPSHOT + 1.0.0 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 eb75a2df4c2..afa75503ee5 100644 --- a/connector/src/main/java/org/geysermc/connector/command/CommandManager.java +++ b/connector/src/main/java/org/geysermc/connector/command/CommandManager.java @@ -51,6 +51,7 @@ public CommandManager(GeyserConnector connector) { 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")); } public void registerCommand(GeyserCommand command) { 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 new file mode 100644 index 00000000000..e29e164b3cb --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/VersionCommand.java @@ -0,0 +1,76 @@ +/* + * 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.MinecraftConstants; +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.command.CommandSender; +import org.geysermc.connector.command.GeyserCommand; +import org.geysermc.connector.utils.FileUtils; +import org.geysermc.connector.utils.LanguageUtils; +import org.geysermc.connector.utils.WebUtils; + +import java.util.Properties; + +public class VersionCommand extends GeyserCommand { + + public GeyserConnector connector; + + public VersionCommand(GeyserConnector connector, String name, String description, String permission) { + super(name, description, permission); + this.connector = connector; + } + + @Override + public void execute(CommandSender sender, String[] args) { + sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.version.version", GeyserConnector.NAME, GeyserConnector.VERSION, MinecraftConstants.GAME_VERSION, GeyserConnector.BEDROCK_PACKET_CODEC.getMinecraftVersion())); + + // Disable update checking in dev mode + //noinspection ConstantConditions - changes in production + if (!GeyserConnector.VERSION.equals("DEV")) { + sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.version.checking")); + try { + Properties gitProp = new Properties(); + gitProp.load(FileUtils.getResource("git.properties")); + + String buildXML = WebUtils.getBody("https://ci.nukkitx.com/job/GeyserMC/job/Geyser/job/" + gitProp.getProperty("git.branch") + "/lastSuccessfulBuild/api/xml?xpath=//master/buildNumber"); + if (buildXML.startsWith("")) { + int latestBuildNum = Integer.parseInt(buildXML.replaceAll("<(\\\\)?buildNumber>", "")); + int buildNum = Integer.parseInt(gitProp.getProperty("git.build.number")); + if (latestBuildNum != buildNum) { + sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.version.no_updates")); + } else { + sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.version.outdated", (latestBuildNum - buildNum), "http://ci.geysermc.org/")); + } + } else { + throw new AssertionError(); + } + } catch (Exception e) { + sender.sendMessage("Failed to check for updates"); + } + } + } +} diff --git a/connector/src/main/resources/languages b/connector/src/main/resources/languages index 08be7fdd7bd..c199011b6c1 160000 --- a/connector/src/main/resources/languages +++ b/connector/src/main/resources/languages @@ -1 +1 @@ -Subproject commit 08be7fdd7bd3c1ade46fa8968c04d3d67bb0d378 +Subproject commit c199011b6c131b195e94d4785abbc3dd73ca19cd From ad751ecb5b55e2a43f5e77c36f272354f3e3aac7 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Mon, 6 Jul 2020 11:18:17 +0100 Subject: [PATCH 43/72] Fix ping passthrough throwing errors on unknown properties (Fixes #903) --- .../java/org/geysermc/connector/common/ping/GeyserPingInfo.java | 1 + 1 file changed, 1 insertion(+) diff --git a/connector/src/main/java/org/geysermc/connector/common/ping/GeyserPingInfo.java b/connector/src/main/java/org/geysermc/connector/common/ping/GeyserPingInfo.java index eff1fe49dca..246a3c6e31f 100644 --- a/connector/src/main/java/org/geysermc/connector/common/ping/GeyserPingInfo.java +++ b/connector/src/main/java/org/geysermc/connector/common/ping/GeyserPingInfo.java @@ -36,6 +36,7 @@ import java.util.Collection; @Data +@JsonIgnoreProperties(ignoreUnknown = true) public class GeyserPingInfo { private String description; From b0e291edc4c62e303cba7002ea47ffde8550d4a1 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Mon, 6 Jul 2020 12:18:14 +0100 Subject: [PATCH 44/72] Fix version checking and add failed language string --- .../connector/command/defaults/VersionCommand.java | 14 +++++++++----- connector/src/main/resources/languages | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) 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 e29e164b3cb..7dd59712ad2 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 @@ -33,6 +33,9 @@ import org.geysermc.connector.utils.LanguageUtils; import org.geysermc.connector.utils.WebUtils; +import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.util.Properties; public class VersionCommand extends GeyserCommand { @@ -56,9 +59,9 @@ public void execute(CommandSender sender, String[] args) { Properties gitProp = new Properties(); gitProp.load(FileUtils.getResource("git.properties")); - String buildXML = WebUtils.getBody("https://ci.nukkitx.com/job/GeyserMC/job/Geyser/job/" + gitProp.getProperty("git.branch") + "/lastSuccessfulBuild/api/xml?xpath=//master/buildNumber"); + String buildXML = WebUtils.getBody("https://ci.nukkitx.com/job/GeyserMC/job/Geyser/job/" + URLEncoder.encode(gitProp.getProperty("git.branch"), StandardCharsets.UTF_8.toString()) + "/lastSuccessfulBuild/api/xml?xpath=//buildNumber"); if (buildXML.startsWith("")) { - int latestBuildNum = Integer.parseInt(buildXML.replaceAll("<(\\\\)?buildNumber>", "")); + 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")); @@ -66,10 +69,11 @@ public void execute(CommandSender sender, String[] args) { sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.version.outdated", (latestBuildNum - buildNum), "http://ci.geysermc.org/")); } } else { - throw new AssertionError(); + throw new AssertionError("buildNumber missing"); } - } catch (Exception e) { - sender.sendMessage("Failed to check for updates"); + } catch (IOException | AssertionError | NumberFormatException e) { + GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.commands.version.failed"), e); + sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.version.failed")); } } } diff --git a/connector/src/main/resources/languages b/connector/src/main/resources/languages index c199011b6c1..fc8f930a238 160000 --- a/connector/src/main/resources/languages +++ b/connector/src/main/resources/languages @@ -1 +1 @@ -Subproject commit c199011b6c131b195e94d4785abbc3dd73ca19cd +Subproject commit fc8f930a238a375c7e3be6e5c7ea1720b61d0cca From 66570a623d2d61674724e0e7e72fef9ce0fbe02f Mon Sep 17 00:00:00 2001 From: theminecoder Date: Mon, 6 Jul 2020 23:10:36 +1000 Subject: [PATCH 45/72] Fix scoreboards bleeding into other servers (#902) --- .../network/translators/java/JavaJoinGameTranslator.java | 3 +++ 1 file changed, 3 insertions(+) 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 5c94d6afb44..01f6b0b92e3 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 @@ -31,6 +31,7 @@ import com.github.steveice10.mc.protocol.packet.ingame.client.ClientSettingsPacket; import org.geysermc.connector.entity.PlayerEntity; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.session.cache.ScoreboardCache; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.utils.DimensionUtils; @@ -58,6 +59,8 @@ public void translate(ServerJoinGamePacket packet, GeyserSession session) { String fakeDim = entity.getDimension().equals(DimensionUtils.OVERWORLD) ? DimensionUtils.NETHER : DimensionUtils.OVERWORLD; DimensionUtils.switchDimension(session, fakeDim); DimensionUtils.switchDimension(session, packet.getDimension()); + + session.getScoreboardCache().removeScoreboard(); } AdventureSettingsPacket bedrockPacket = new AdventureSettingsPacket(); From 3cdc208174d91d7a04cda0b7637b1598c9f2dfc7 Mon Sep 17 00:00:00 2001 From: James Harrison Date: Mon, 6 Jul 2020 14:26:00 +0100 Subject: [PATCH 46/72] Update MinecraftCapes endpoints (#907) Updates the mccapes endpoints with the new domain. --- .../main/java/org/geysermc/connector/utils/SkinProvider.java | 4 ++-- 1 file changed, 2 insertions(+), 2 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 aea9ba1892b..aa01ada8142 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java @@ -537,7 +537,7 @@ public enum CapeProvider { OPTIFINE("http://s.optifine.net/capes/%s.png", CapeUrlType.USERNAME), LABYMOD("https://www.labymod.net/page/php/getCapeTexture.php?uuid=%s", CapeUrlType.UUID_DASHED), FIVEZIG("https://textures.5zigreborn.eu/profile/%s", CapeUrlType.UUID_DASHED), - MINECRAFTCAPES("https://www.minecraftcapes.co.uk/getCape/%s", CapeUrlType.UUID); + MINECRAFTCAPES("https://minecraftcapes.net/profile/%s/cape", CapeUrlType.UUID); public static final CapeProvider[] VALUES = Arrays.copyOfRange(values(), 1, 5); private String url; @@ -573,7 +573,7 @@ public enum CapeUrlType { @NoArgsConstructor @Getter public enum EarsProvider { - MINECRAFTCAPES("https://www.minecraftcapes.co.uk/getEars/%s", CapeUrlType.UUID); + MINECRAFTCAPES("https://minecraftcapes.net/profile/%s/ears", CapeUrlType.UUID); public static final EarsProvider[] VALUES = values(); private String url; From 7e51040a8e0081854d852ceda709e91169fd82b6 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Mon, 6 Jul 2020 14:41:55 +0100 Subject: [PATCH 47/72] Fix fallback locale not loading --- .../main/java/org/geysermc/connector/utils/LanguageUtils.java | 1 + 1 file changed, 1 insertion(+) 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 6172cc9e8b0..9dabc387161 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java @@ -173,6 +173,7 @@ public static String getDefaultLocale() { locale = formatLocale(Locale.getDefault().getLanguage() + "_" + Locale.getDefault().getCountry()); if (!isValidLanguage(locale)) { // Bedrock does not support this language locale = "en_US"; + loadGeyserLocale(locale); } if (GeyserConnector.getInstance() != null && GeyserConnector.getInstance().getConfig() != null && (GeyserConnector.getInstance().getConfig().getDefaultLocale() == null || !isValid)) { // Means we should use the system locale for sure From 82c6276794b3f3b26e9cbc3b5e5f9688ed097d13 Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Mon, 6 Jul 2020 15:19:48 -0400 Subject: [PATCH 48/72] Move back to using the main repository for MCProtocolLib --- connector/pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/connector/pom.xml b/connector/pom.xml index b2bc15263cc..b9bf67cef91 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -103,9 +103,9 @@ compile - com.github.GeyserMC - MCProtocolLib - feature~1.16-1.12.1-1-g10bb8e2-319 + com.github.steveice10 + mcprotocollib + 7545884a2d compile From 545dfa38f0db781fc6199ee9a8efcccb1815b1f4 Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Mon, 6 Jul 2020 16:22:07 -0400 Subject: [PATCH 49/72] JavaUpdateTileEntityTranslator improvements - Remove the use of deprecated functions - Check for empty NBT (fixes errors on CubeCraft) --- .../java/world/JavaUpdateTileEntityTranslator.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateTileEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateTileEntityTranslator.java index 822be8c8939..ae4ed779296 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateTileEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateTileEntityTranslator.java @@ -40,12 +40,17 @@ public class JavaUpdateTileEntityTranslator extends PacketTranslator Date: Mon, 6 Jul 2020 23:36:04 +0100 Subject: [PATCH 50/72] Fix RGB colors on signs causing chunk issues, fix items names not being displayed correctly --- connector/pom.xml | 12 +++++----- .../translators/item/ItemTranslator.java | 23 +++++++++---------- .../translators/nbt/BasicItemTranslator.java | 12 +++++----- .../connector/utils/MessageUtils.java | 10 ++++---- 4 files changed, 28 insertions(+), 29 deletions(-) diff --git a/connector/pom.xml b/connector/pom.xml index b9bf67cef91..3fdd5f37a96 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -127,20 +127,20 @@ net.kyori - text-api - 3.0.3 + adventure-api + 4.0.0-SNAPSHOT compile net.kyori - text-serializer-gson - 3.0.3 + adventure-text-serializer-gson + 4.0.0-SNAPSHOT compile net.kyori - text-serializer-legacy - 3.0.3 + adventure-text-serializer-legacy + 4.0.0-SNAPSHOT compile 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 7811d9c0ae2..fde799fe497 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 @@ -28,17 +28,7 @@ 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.ByteArrayTag; -import com.github.steveice10.opennbt.tag.builtin.ByteTag; -import com.github.steveice10.opennbt.tag.builtin.DoubleTag; -import com.github.steveice10.opennbt.tag.builtin.FloatTag; -import com.github.steveice10.opennbt.tag.builtin.IntArrayTag; -import com.github.steveice10.opennbt.tag.builtin.IntTag; -import com.github.steveice10.opennbt.tag.builtin.ListTag; -import com.github.steveice10.opennbt.tag.builtin.LongArrayTag; -import com.github.steveice10.opennbt.tag.builtin.LongTag; -import com.github.steveice10.opennbt.tag.builtin.ShortTag; -import com.github.steveice10.opennbt.tag.builtin.StringTag; +import com.github.steveice10.opennbt.tag.builtin.*; import com.nukkitx.nbt.NbtList; import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtMapBuilder; @@ -46,11 +36,14 @@ 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.TextComponent; +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.utils.MessageUtils; import org.geysermc.connector.utils.LanguageUtils; +import org.geysermc.connector.utils.MessageUtils; import org.reflections.Reflections; import java.util.*; @@ -167,6 +160,12 @@ public static ItemData translateToBedrock(GeyserSession session, ItemStack stack if (display != null) { String name = display.getString("Name"); + // If its not a message convert it + if (!MessageUtils.isMessage(name)) { + TextComponent component = LegacyComponentSerializer.legacy().deserialize(name); + name = GsonComponentSerializer.gson().serialize(component); + } + // Check if its a message to translate if (MessageUtils.isMessage(name)) { // Get the translated name 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 776cec72912..34c7f43dc57 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 @@ -29,13 +29,13 @@ 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 net.kyori.text.Component; -import net.kyori.text.TextComponent; -import net.kyori.text.serializer.gson.GsonComponentSerializer; -import net.kyori.text.serializer.legacy.LegacyComponentSerializer; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; 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.network.translators.item.NbtItemStackTranslator; import org.geysermc.connector.utils.MessageUtils; import java.util.ArrayList; @@ -101,7 +101,7 @@ private String toJavaMessage(StringTag tag) { message = message.replaceFirst("§r", ""); } Component component = TextComponent.of(message); - return GsonComponentSerializer.INSTANCE.serialize(component); + return GsonComponentSerializer.gson().serialize(component); } private String toBedrockMessage(StringTag tag) { 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 3a35782d4da..bf19622466a 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java @@ -35,9 +35,9 @@ import com.github.steveice10.mc.protocol.data.message.style.MessageStyle; import com.google.gson.JsonObject; import com.google.gson.JsonParser; -import net.kyori.text.Component; -import net.kyori.text.serializer.gson.GsonComponentSerializer; -import net.kyori.text.serializer.legacy.LegacyComponentSerializer; +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.*; @@ -263,12 +263,12 @@ public static String getBedrockMessage(String message) { } public static Component phraseJavaMessage(String message) { - return GsonComponentSerializer.INSTANCE.deserialize(message); + return GsonComponentSerializer.gson().deserialize(message); } public static String getJavaMessage(String message) { Component component = LegacyComponentSerializer.legacy().deserialize(message); - return GsonComponentSerializer.INSTANCE.serialize(component); + return GsonComponentSerializer.gson().serialize(component); } /** From c454e443df0fc2829732d8b44d425619be79e44a Mon Sep 17 00:00:00 2001 From: rtm516 Date: Mon, 6 Jul 2020 23:36:31 +0100 Subject: [PATCH 51/72] Fix maps with negative IDs causing out of bounds errors --- .../bedrock/BedrockMapInfoRequestTranslator.java | 8 ++++++-- .../translators/java/world/JavaMapDataTranslator.java | 6 +++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMapInfoRequestTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMapInfoRequestTranslator.java index 3c7efa18bf0..247021f10c9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMapInfoRequestTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMapInfoRequestTranslator.java @@ -39,12 +39,16 @@ public class BedrockMapInfoRequestTranslator extends PacketTranslator { - session.sendUpstreamPacket(session.getStoredMaps().get(mapID)); - session.getStoredMaps().remove(mapID); + session.sendUpstreamPacket(session.getStoredMaps().get(finalMapID)); + session.getStoredMaps().remove(finalMapID); }, 100, TimeUnit.MILLISECONDS); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaMapDataTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaMapDataTranslator.java index 2aee7bc0276..12ef1053b92 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaMapDataTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaMapDataTranslator.java @@ -85,7 +85,11 @@ public void translate(ServerMapDataPacket packet, GeyserSession session) { // Store the map to send when the client requests it, as bedrock expects the data after a MapInfoRequestPacket if (shouldStore) { - session.getStoredMaps().put(mapItemDataPacket.getUniqueMapId(), mapItemDataPacket); + long uniqueMapId = mapItemDataPacket.getUniqueMapId(); + if (uniqueMapId <= -1l) { + uniqueMapId = 0l; + } + session.getStoredMaps().put(uniqueMapId, mapItemDataPacket); } // Send anyway just in case From 02905c2a355d53df805d7b8d52b2f0b92dd08912 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Mon, 6 Jul 2020 23:41:54 +0100 Subject: [PATCH 52/72] Add the adventure-api maven repo --- pom.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pom.xml b/pom.xml index 3e119eb1197..acfdc3d6346 100644 --- a/pom.xml +++ b/pom.xml @@ -65,6 +65,10 @@ viaversion-repo https://repo.viaversion.com + + sonatype + https://oss.sonatype.org/content/repositories/snapshots/ + From 699402e635919fdd1680cf7361cd4042264c382c Mon Sep 17 00:00:00 2001 From: AJ Ferguson Date: Mon, 6 Jul 2020 15:52:38 -0800 Subject: [PATCH 53/72] Fix bug with maps --- .../connector/network/session/GeyserSession.java | 3 ++- .../bedrock/BedrockMapInfoRequestTranslator.java | 11 +++++------ .../translators/java/world/JavaMapDataTranslator.java | 6 +----- 3 files changed, 8 insertions(+), 12 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 666820cc290..6b51c8921ea 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 @@ -48,6 +48,7 @@ import com.nukkitx.protocol.bedrock.data.*; import com.nukkitx.protocol.bedrock.packet.*; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectMaps; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2LongMap; import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap; @@ -107,7 +108,7 @@ public class GeyserSession implements CommandSender { private TeleportCache teleportCache; @Getter - private final Long2ObjectMap storedMaps = new Long2ObjectOpenHashMap<>(); + private final Long2ObjectMap storedMaps = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>()); /** * A map of Vector3i positions to Java entity IDs. diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMapInfoRequestTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMapInfoRequestTranslator.java index 247021f10c9..11dfe46e091 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMapInfoRequestTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMapInfoRequestTranslator.java @@ -25,6 +25,7 @@ package org.geysermc.connector.network.translators.bedrock; +import com.nukkitx.protocol.bedrock.packet.ClientboundMapItemDataPacket; import com.nukkitx.protocol.bedrock.packet.MapInfoRequestPacket; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; @@ -39,16 +40,14 @@ public class BedrockMapInfoRequestTranslator extends PacketTranslator { - session.sendUpstreamPacket(session.getStoredMaps().get(finalMapID)); - session.getStoredMaps().remove(finalMapID); + ClientboundMapItemDataPacket mapPacket = session.getStoredMaps().remove(mapID); + if (mapPacket != null) { + session.sendUpstreamPacket(mapPacket); + } }, 100, TimeUnit.MILLISECONDS); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaMapDataTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaMapDataTranslator.java index 12ef1053b92..2aee7bc0276 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaMapDataTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaMapDataTranslator.java @@ -85,11 +85,7 @@ public void translate(ServerMapDataPacket packet, GeyserSession session) { // Store the map to send when the client requests it, as bedrock expects the data after a MapInfoRequestPacket if (shouldStore) { - long uniqueMapId = mapItemDataPacket.getUniqueMapId(); - if (uniqueMapId <= -1l) { - uniqueMapId = 0l; - } - session.getStoredMaps().put(uniqueMapId, mapItemDataPacket); + session.getStoredMaps().put(mapItemDataPacket.getUniqueMapId(), mapItemDataPacket); } // Send anyway just in case From 24f9651cc6e6279e3a2b709c0cf6766c2032fb4a Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Mon, 6 Jul 2020 20:11:34 -0500 Subject: [PATCH 54/72] Convert map of players to list (may address #833) --- .../spigot/world/GeyserSpigotBlockPlaceListener.java | 2 +- .../platform/standalone/gui/GeyserStandaloneGUI.java | 7 +++---- .../java/org/geysermc/connector/GeyserConnector.java | 12 ++++++------ .../connector/command/defaults/ListCommand.java | 4 ++-- .../connector/command/defaults/OffhandCommand.java | 2 +- .../connector/command/defaults/ReloadCommand.java | 2 +- .../geysermc/connector/entity/FishingHookEntity.java | 2 +- .../network/ConnectorServerEventHandler.java | 9 --------- .../connector/network/session/GeyserSession.java | 9 ++++++++- 9 files changed, 23 insertions(+), 26 deletions(-) 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 f17a97e3766..4fe93d45920 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 @@ -47,7 +47,7 @@ public class GeyserSpigotBlockPlaceListener implements Listener { @EventHandler public void place(final BlockPlaceEvent event) { - for (GeyserSession session : connector.getPlayers().values()) { + for (GeyserSession session : connector.getPlayers()) { if (event.getPlayer() == Bukkit.getPlayer(session.getPlayerEntity().getUsername())) { LevelSoundEventPacket placeBlockSoundPacket = new LevelSoundEventPacket(); placeBlockSoundPacket.setSound(SoundEvent.PLACE); 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 9be0e1c6a1f..0c0ec112702 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 @@ -42,7 +42,6 @@ import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; -import java.net.InetSocketAddress; import java.net.URL; import java.util.ArrayList; import java.util.List; @@ -292,10 +291,10 @@ public void startUpdateThread() { // Update player table playerTableModel.getDataVector().removeAllElements(); - for (Map.Entry player : GeyserConnector.getInstance().getPlayers().entrySet()) { + for (GeyserSession player : GeyserConnector.getInstance().getPlayers()) { Vector row = new Vector(); - row.add(player.getKey().getHostName()); - row.add(player.getValue().getPlayerEntity().getUsername()); + row.add(player.getSocketAddress().getHostName()); + row.add(player.getPlayerEntity().getUsername()); playerTableModel.addRow(row); } diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 298f5bc8270..675c2b9a290 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -59,8 +59,8 @@ import java.net.InetSocketAddress; import java.text.DecimalFormat; -import java.util.HashMap; -import java.util.Map; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -76,7 +76,7 @@ public class GeyserConnector { public static final String NAME = "Geyser"; public static final String VERSION = "DEV"; // A fallback for running in IDEs - private final Map players = new HashMap<>(); + private final List players = new ArrayList<>(); private static GeyserConnector instance; @@ -185,7 +185,7 @@ public void shutdown() { if (players.size() >= 1) { bootstrap.getGeyserLogger().info(LanguageUtils.getLocaleStringLog("geyser.core.shutdown.kick.log", players.size())); - for (GeyserSession playerSession : players.values()) { + for (GeyserSession playerSession : players) { playerSession.disconnect(LanguageUtils.getPlayerLocaleString("geyser.core.shutdown.kick.message", playerSession.getClientData().getLanguageCode())); } @@ -227,11 +227,11 @@ public void run() { } public void addPlayer(GeyserSession player) { - players.put(player.getSocketAddress(), player); + players.add(player); } public void removePlayer(GeyserSession player) { - players.remove(player.getSocketAddress()); + players.remove(player); } public static GeyserConnector start(PlatformType platformType, GeyserBootstrap bootstrap) { 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 0de73a5d154..3c78c554000 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 @@ -47,9 +47,9 @@ public ListCommand(GeyserConnector connector, String name, String description, S 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().values().stream().map(GeyserSession::getName).collect(Collectors.joining(" "))); + 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().values().stream().map(GeyserSession::getName).collect(Collectors.joining(" "))); + message = LanguageUtils.getLocaleStringLog("geyser.commands.list.message", 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 a49506b0f0f..b1b6013286d 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 @@ -59,7 +59,7 @@ public void execute(CommandSender sender, String[] args) { return; } // Needed for Bukkit - sender is not an instance of GeyserSession - for (GeyserSession session : connector.getPlayers().values()) { + for (GeyserSession session : connector.getPlayers()) { if (sender.getName().equals(session.getPlayerEntity().getUsername())) { ClientPlayerActionPacket releaseItemPacket = new ClientPlayerActionPacket(PlayerAction.SWAP_HANDS, new Position(0,0,0), BlockFace.DOWN); 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 d8bf8583b03..9c24fa2be15 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 @@ -56,7 +56,7 @@ public void execute(CommandSender sender, String[] args) { sender.sendMessage(message); - for (GeyserSession session : connector.getPlayers().values()) { + for (GeyserSession session : connector.getPlayers()) { session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.commands.reload.kick", session.getClientData().getLanguageCode())); } connector.reload(); diff --git a/connector/src/main/java/org/geysermc/connector/entity/FishingHookEntity.java b/connector/src/main/java/org/geysermc/connector/entity/FishingHookEntity.java index cc7d749d99d..4d0eb202f24 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/FishingHookEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/FishingHookEntity.java @@ -37,7 +37,7 @@ public class FishingHookEntity extends Entity { public FishingHookEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation, ProjectileData data) { super(entityId, geyserId, entityType, position, motion, rotation); - for (GeyserSession session : GeyserConnector.getInstance().getPlayers().values()) { + for (GeyserSession session : GeyserConnector.getInstance().getPlayers()) { Entity entity = session.getEntityCache().getEntityByJavaId(data.getOwnerId()); if (entity == null && session.getPlayerEntity().getEntityId() == data.getOwnerId()) { entity = session.getPlayerEntity(); 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 ad5cb42aa9b..6ca9063c222 100644 --- a/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java @@ -108,15 +108,6 @@ public BedrockPong onQuery(InetSocketAddress inetSocketAddress) { public void onSessionCreation(BedrockServerSession bedrockServerSession) { bedrockServerSession.setLogging(true); bedrockServerSession.setPacketHandler(new UpstreamPacketHandler(connector, new GeyserSession(connector, bedrockServerSession))); - bedrockServerSession.addDisconnectHandler(disconnectReason -> { - connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.disconnect", bedrockServerSession.getAddress().getAddress(), disconnectReason)); - - GeyserSession player = connector.getPlayers().get(bedrockServerSession.getAddress()); - if (player != null) { - player.disconnect(disconnectReason.name()); - connector.removePlayer(player); - } - }); bedrockServerSession.setPacketCodec(GeyserConnector.BEDROCK_PACKET_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 6b51c8921ea..0aa0ceaae1e 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 @@ -210,6 +210,13 @@ public GeyserSession(GeyserConnector connector, BedrockServerSession bedrockServ this.loggedIn = false; this.inventoryCache.getInventories().put(0, inventory); + + bedrockServerSession.addDisconnectHandler(disconnectReason -> { + connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.disconnect", bedrockServerSession.getAddress().getAddress(), disconnectReason)); + + disconnect(disconnectReason.name()); + connector.removePlayer(this); + }); } public void connect(RemoteServer remoteServer) { @@ -428,7 +435,7 @@ public void disconnect(String reason) { downstream.getSession().disconnect(reason); } if (upstream != null && !upstream.isClosed()) { - connector.getPlayers().remove(this.upstream.getAddress()); + connector.getPlayers().remove(this); upstream.disconnect(reason); } } From f68632f43349816528efc8719e01817ff4d518fc Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Mon, 6 Jul 2020 21:38:10 -0400 Subject: [PATCH 55/72] Block-related updates - Fix block breaking animation - Fix block breaking particles - Don't initialize Geyser's chunk cache if using Spigot --- .../connector/network/session/cache/ChunkCache.java | 11 +++++++---- .../translators/bedrock/BedrockActionTranslator.java | 2 +- .../BedrockInventoryTransactionTranslator.java | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) 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 a7b0c96653f..9601a2981c5 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 @@ -29,6 +29,7 @@ import com.github.steveice10.mc.protocol.data.game.chunk.Column; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import lombok.Getter; +import org.geysermc.connector.bootstrap.GeyserBootstrap; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.network.translators.world.chunk.ChunkPosition; @@ -38,15 +39,17 @@ public class ChunkCache { - private boolean cache; - private final GeyserSession session; + private final boolean cache; @Getter private Map chunks = new HashMap<>(); public ChunkCache(GeyserSession session) { - this.session = session; - this.cache = session.getConnector().getConfig().isCacheChunks(); + if (session.getConnector().getWorldManager().getClass() == GeyserBootstrap.DEFAULT_CHUNK_MANAGER.getClass()) { + this.cache = session.getConnector().getConfig().isCacheChunks(); + } else { + this.cache = false; // To prevent Spigot from initializing + } } public void addToCache(Column chunk) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockActionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockActionTranslator.java index 7d8772fbdfb..dfd49f2ee1a 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockActionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockActionTranslator.java @@ -112,7 +112,7 @@ public void translate(PlayerActionPacket packet, GeyserSession session) { break; case CONTINUE_BREAK: LevelEventPacket continueBreakPacket = new LevelEventPacket(); - continueBreakPacket.setType(LevelEventType.BLOCK_UPDATE_BREAK); //TODO: There are now level event types for all breaks. Important? + continueBreakPacket.setType(LevelEventType.PARTICLE_CRACK_BLOCK); continueBreakPacket.setData(BlockTranslator.getBedrockBlockId(session.getBreakingBlock())); continueBreakPacket.setPosition(packet.getBlockPosition().toFloat()); session.sendUpstreamPacket(continueBreakPacket); 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 f05c46f2497..25e1c54e2a5 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 @@ -152,7 +152,7 @@ public void translate(InventoryTransactionPacket packet, GeyserSession session) session.setLastBlockPlacePosition(null); LevelEventPacket blockBreakPacket = new LevelEventPacket(); - blockBreakPacket.setType(LevelEventType.BLOCK_STOP_BREAK); //TODO: Make sure this is the right new enum + blockBreakPacket.setType(LevelEventType.PARTICLE_DESTROY_BLOCK); blockBreakPacket.setPosition(packet.getBlockPosition().toFloat()); blockBreakPacket.setData(BlockTranslator.getBedrockBlockId(blockState)); session.sendUpstreamPacket(blockBreakPacket); From 4353c184d9d9962769baf330181dcac9f89b9989 Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Mon, 6 Jul 2020 21:42:12 -0400 Subject: [PATCH 56/72] Revert all 1.14.60-specific changes --- bootstrap/bukkit/pom.xml | 2 +- .../java/org/geysermc/platform/bukkit/GeyserBukkitPlugin.java | 4 ++-- .../src/main/java/org/geysermc/connector/GeyserConnector.java | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/bootstrap/bukkit/pom.xml b/bootstrap/bukkit/pom.xml index ca8188126ef..95926854dec 100644 --- a/bootstrap/bukkit/pom.xml +++ b/bootstrap/bukkit/pom.xml @@ -26,7 +26,7 @@ us.myles viaversion - 3.0.1 + 3.0.0-SNAPSHOT provided diff --git a/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitPlugin.java b/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitPlugin.java index 1507f9ee45e..3a13777b47c 100644 --- a/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitPlugin.java +++ b/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitPlugin.java @@ -110,8 +110,8 @@ public void onEnable() { if (Bukkit.getPluginManager().getPlugin("ViaVersion") != null) { // TODO: Update when ViaVersion updates // API changes between 2.2.3 and 3.0.0-SNAPSHOT require this check - if (Via.getAPI().getVersion().contains("2.2.3") && isLegacy) { - geyserLogger.info("ViaVersion detected but not the newest version of ViaVersion. Please update your ViaVersion plugin for compatibility with Geyser."); + if (!Via.getAPI().getVersion().equals("3.0.0-SNAPSHOT") && isLegacy) { + geyserLogger.info("ViaVersion detected but not ViaVersion-ABSTRACTION. Please update your ViaVersion plugin for compatibility with Geyser."); } else { isViaVersion = true; } diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 3d55845dc43..d201656ad25 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -110,7 +110,6 @@ private GeyserConnector(PlatformType platformType, GeyserBootstrap bootstrap) { logger.info("Loading " + NAME + " version " + VERSION); logger.info(""); logger.info("******************************************"); - logger.warning("This build is for connecting to Java 1.15.2 servers and from Bedrock 1.14.60 clients. Please join our Discord (https://discord.geysermc.org) for up-to-date information about where to download 1.16."); this.generalThreadPool = Executors.newScheduledThreadPool(config.getGeneralThreadPool()); From 9359f401f86fa591af8ac6d968eb6fa70312c983 Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Mon, 6 Jul 2020 21:54:33 -0400 Subject: [PATCH 57/72] Don't use 1.16 branch of mappings --- .gitmodules | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 35887822e5c..0c014712bd7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,7 +1,6 @@ [submodule "connector/src/main/resources/mappings"] path = connector/src/main/resources/mappings url = https://github.com/GeyserMC/mappings.git - branch = feature/1.16 [submodule "connector/src/main/resources/languages"] path = connector/src/main/resources/languages url = https://github.com/GeyserMC/languages.git From 50176e10a87ea4a566e373389f88a6d770cff6a2 Mon Sep 17 00:00:00 2001 From: AJ Ferguson Date: Mon, 6 Jul 2020 23:44:39 -0800 Subject: [PATCH 58/72] Fix inabilty to place items into brewing stand --- .../geysermc/connector/GeyserConnector.java | 2 + .../translators/item/PotionMixRegistry.java | 113 ++++++++++++++++++ .../java/JavaDeclareRecipesTranslator.java | 8 +- 3 files changed, 117 insertions(+), 6 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/item/PotionMixRegistry.java diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 675c2b9a290..601421303f1 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -47,6 +47,7 @@ import org.geysermc.connector.network.translators.effect.EffectRegistry; import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.network.translators.item.ItemTranslator; +import org.geysermc.connector.network.translators.item.PotionMixRegistry; import org.geysermc.connector.network.translators.sound.SoundHandlerRegistry; import org.geysermc.connector.network.translators.sound.SoundRegistry; import org.geysermc.connector.network.translators.world.WorldManager; @@ -127,6 +128,7 @@ private GeyserConnector(PlatformType platformType, GeyserBootstrap bootstrap) { ItemRegistry.init(); ItemTranslator.init(); LocaleUtils.init(); + PotionMixRegistry.init(); SoundRegistry.init(); SoundHandlerRegistry.init(); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/PotionMixRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/PotionMixRegistry.java new file mode 100644 index 00000000000..02e01d83c9a --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/PotionMixRegistry.java @@ -0,0 +1,113 @@ +/* + * 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.item; + +import com.nukkitx.protocol.bedrock.data.inventory.PotionMixData; + +import java.util.*; + +/** + * Generates a {@link Collection} of {@link PotionMixData} that enables the + * Bedrock client to place brewing items into the brewing stand. + * (Does not contain actual potion mixes.) + * + * Designed to replicate Java Edition behavior. + * (Ex: Bedrock cannot normally place glass bottles or fully upgraded + * potions into the brewing stand, but Java can.) + */ +public class PotionMixRegistry { + public static final Collection POTION_MIXES; + + private PotionMixRegistry() { + } + + public static void init() { + // no-op + } + + static { + List ingredients = new ArrayList<>(); + ingredients.add(getNonNull("minecraft:nether_wart")); + ingredients.add(getNonNull("minecraft:redstone")); + ingredients.add(getNonNull("minecraft:glowstone_dust")); + ingredients.add(getNonNull("minecraft:fermented_spider_eye")); + ingredients.add(getNonNull("minecraft:gunpowder")); + ingredients.add(getNonNull("minecraft:dragon_breath")); + ingredients.add(getNonNull("minecraft:sugar")); + ingredients.add(getNonNull("minecraft:rabbit_foot")); + ingredients.add(getNonNull("minecraft:glistering_melon_slice")); + ingredients.add(getNonNull("minecraft:spider_eye")); + ingredients.add(getNonNull("minecraft:pufferfish")); + ingredients.add(getNonNull("minecraft:magma_cream")); + ingredients.add(getNonNull("minecraft:golden_carrot")); + ingredients.add(getNonNull("minecraft:blaze_powder")); + ingredients.add(getNonNull("minecraft:ghast_tear")); + ingredients.add(getNonNull("minecraft:turtle_helmet")); + ingredients.add(getNonNull("minecraft:phantom_membrane")); + + List inputs = new ArrayList<>(); + inputs.add(getNonNull("minecraft:potion")); + inputs.add(getNonNull("minecraft:splash_potion")); + inputs.add(getNonNull("minecraft:lingering_potion")); + + ItemEntry glassBottle = getNonNull("minecraft:glass_bottle"); + + Set potionMixes = new HashSet<>(); + + // Add all types of potions as inputs + ItemEntry fillerIngredient = ingredients.get(0); + for (ItemEntry input : inputs) { + for (Potion potion : Potion.values()) { + potionMixes.add(new PotionMixData( + input.getBedrockId(), potion.getBedrockId(), + fillerIngredient.getBedrockId(), fillerIngredient.getBedrockData(), + glassBottle.getBedrockId(), glassBottle.getBedrockData()) + ); + } + } + + // Add all brewing ingredients + // Also adds glass bottle as input + for (ItemEntry ingredient : ingredients) { + potionMixes.add(new PotionMixData( + glassBottle.getBedrockId(), glassBottle.getBedrockData(), + ingredient.getBedrockId(), ingredient.getBedrockData(), + glassBottle.getBedrockId(), glassBottle.getBedrockData()) + ); + } + + POTION_MIXES = potionMixes; + } + + private static ItemEntry getNonNull(String javaIdentifier) { + ItemEntry itemEntry = ItemRegistry.getItemEntry(javaIdentifier); + if (itemEntry == null) + throw new NullPointerException("No item entry exists for java identifier: " + javaIdentifier); + + return itemEntry; + } +} 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 08022640f2c..75ccc0a5a0a 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 @@ -33,7 +33,6 @@ import com.nukkitx.nbt.NbtMap; import com.nukkitx.protocol.bedrock.data.inventory.CraftingData; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; -import com.nukkitx.protocol.bedrock.data.inventory.PotionMixData; import com.nukkitx.protocol.bedrock.packet.CraftingDataPacket; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; @@ -45,16 +44,13 @@ 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.PotionMixRegistry; import java.util.*; import java.util.stream.Collectors; @Translator(packet = ServerDeclareRecipesPacket.class) public class JavaDeclareRecipesTranslator extends PacketTranslator { - private static final Collection POTION_MIXES = - Arrays.stream(new int[]{372, 331, 348, 376, 289, 437, 353, 414, 382, 375, 462, 378, 396, 377, 370, 469, 470}) - .mapToObj(ingredient -> new PotionMixData(0, ingredient, 0, 0, 0, 0)) //TODO: Confirm this is correct behavior. - .collect(Collectors.toList()); @Override public void translate(ServerDeclareRecipesPacket packet, GeyserSession session) { @@ -89,7 +85,7 @@ public void translate(ServerDeclareRecipesPacket packet, GeyserSession session) } } } - craftingDataPacket.getPotionMixData().addAll(POTION_MIXES); + craftingDataPacket.getPotionMixData().addAll(PotionMixRegistry.POTION_MIXES); session.sendUpstreamPacket(craftingDataPacket); } From f9760b721c62749141e8bf07b58a393480661f0b Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Tue, 7 Jul 2020 08:30:11 -0400 Subject: [PATCH 59/72] Don't process the display tag if it's empty --- .../connector/network/translators/item/ItemTranslator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 fde799fe497..e28e8b0c8c7 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 @@ -157,7 +157,7 @@ public static ItemData translateToBedrock(GeyserSession session, ItemStack stack NbtMap tag = itemData.getTag(); if (tag != null) { NbtMap display = tag.getCompound("display"); - if (display != null) { + if (display != null && !display.isEmpty()) { String name = display.getString("Name"); // If its not a message convert it From 8807d5d9c6b96180ffc14517b07c9933318aac01 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Tue, 7 Jul 2020 14:30:33 +0100 Subject: [PATCH 60/72] Fix banner block patterns --- .../world/block/entity/BannerBlockEntityTranslator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 e2a555090d9..9e86cb4cfb6 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 @@ -55,7 +55,7 @@ public Map translateTag(CompoundTag tag, int blockState) { if (tag.contains("Patterns")) { ListTag patterns = tag.get("Patterns"); - tags.put("", BannerTranslator.convertBannerPattern(patterns)); + tags.put("Patterns", BannerTranslator.convertBannerPattern(patterns)); } if (tag.contains("CustomName")) { From 75f2891ec0fe093ff88891434e7a24df58591ea2 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Tue, 7 Jul 2020 14:47:56 +0100 Subject: [PATCH 61/72] Fix map_uuid nbt type --- .../translators/item/translators/nbt/MapItemTranslator.java | 6 +++--- 1 file changed, 3 insertions(+), 3 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 51029b83dcb..4a36880c900 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 @@ -28,10 +28,10 @@ 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.StringTag; +import com.github.steveice10.opennbt.tag.builtin.LongTag; 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.network.translators.item.NbtItemStackTranslator; @ItemRemapper public class MapItemTranslator extends NbtItemStackTranslator { @@ -41,7 +41,7 @@ public void translateToBedrock(CompoundTag itemTag, ItemEntry itemEntry) { IntTag mapId = itemTag.get("map"); if (mapId != null) { - itemTag.put(new StringTag("map_uuid", mapId.getValue().toString())); + 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"); From a16deb269a1f4553d9277ee998f71be67b89f74a Mon Sep 17 00:00:00 2001 From: rtm516 Date: Tue, 7 Jul 2020 15:09:24 +0100 Subject: [PATCH 62/72] Fix exact color matches not being formatted correctly (Fixes #912) --- .../main/java/org/geysermc/connector/utils/MessageUtils.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 bf19622466a..44536ccf602 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java @@ -391,7 +391,8 @@ private static String getClosestColor(String color) { for (Map.Entry testColor : COLORS.entrySet()) { if (testColor.getValue() == rgb) { - return testColor.getKey(); + closest = testColor.getKey(); + break; } int testR = (testColor.getValue() >> 16) & 0xFF; From c2be7a181da8c72bbb143aed318b185b62b77dda Mon Sep 17 00:00:00 2001 From: rtm516 Date: Tue, 7 Jul 2020 16:11:52 +0100 Subject: [PATCH 63/72] Fix Piglin bartering animation (Fixes #863) --- .../connector/entity/living/monster/PiglinEntity.java | 10 ++++++++++ .../network/translators/item/ItemRegistry.java | 2 ++ 2 files changed, 12 insertions(+) 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 78a420b8c40..ae9319efa9f 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 @@ -31,6 +31,7 @@ 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.network.translators.item.ItemRegistry; public class PiglinEntity extends MonsterEntity { @@ -56,4 +57,13 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s super.updateBedrockMetadata(entityMetadata, session); } + + @Override + public void updateEquipment(GeyserSession session) { + // Check if the Piglin is holding Gold and set the ADMIRING flag accordingly + metadata.getFlags().setFlag(EntityFlag.ADMIRING, offHand.getId() == ItemRegistry.GOLD); + super.updateBedrockMetadata(session); + + super.updateEquipment(session); + } } 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 23c566d7c9b..bf3769ed993 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 @@ -60,6 +60,8 @@ public class ItemRegistry { public static final int SHIELD = 829; // Boat ID, used in BedrockInventoryTransactionTranslator.java public static final int BOAT = 333; + // Gold ID, used in PiglinEntity.java + public static final int GOLD = 266; public static int BARRIER_INDEX = 0; From 5f6566ad0efb147c384f398b76934bb1da251f26 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Tue, 7 Jul 2020 16:23:21 +0100 Subject: [PATCH 64/72] Move to dynamic item ID mapping in ItemRegistry --- .../translators/item/ItemRegistry.java | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) 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 bf3769ed993..b9ce5c6c111 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 @@ -57,11 +57,11 @@ public class ItemRegistry { public static final Int2ObjectMap ITEM_ENTRIES = new Int2ObjectOpenHashMap<>(); // Shield ID, used in Entity.java - public static final int SHIELD = 829; + public static int SHIELD = 0; // Boat ID, used in BedrockInventoryTransactionTranslator.java - public static final int BOAT = 333; + public static int BOAT = 0; // Gold ID, used in PiglinEntity.java - public static final int GOLD = 266; + public static int GOLD = 0; public static int BARRIER_INDEX = 0; @@ -85,6 +85,20 @@ public static void init() { for (JsonNode entry : itemEntries) { ITEMS.add(new StartGamePacket.ItemEntry(entry.get("name").textValue(), (short) entry.get("id").intValue())); + + switch (entry.get("name").textValue()) { + case "minecraft:shield": + SHIELD = entry.get("id").intValue(); + break; + case "minecraft:boat": + BOAT = entry.get("id").intValue(); + break; + case "minecraft:gold_ingot": + GOLD = entry.get("id").intValue(); + break; + default: + break; + } } stream = FileUtils.getResource("mappings/items.json"); From 0cea703b465daba3714011fc1fa7b86435581601 Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Tue, 7 Jul 2020 16:40:19 -0400 Subject: [PATCH 65/72] Save ItemEntry classes for items --- .../org/geysermc/connector/entity/Entity.java | 6 +-- .../entity/living/monster/PiglinEntity.java | 2 +- .../bedrock/BedrockInteractTranslator.java | 2 +- ...BedrockInventoryTransactionTranslator.java | 4 +- .../network/translators/item/ItemEntry.java | 2 + .../translators/item/ItemRegistry.java | 37 +++++++++---------- 6 files changed, 27 insertions(+), 26 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 ffe13a50d72..1618d6afa36 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/Entity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/Entity.java @@ -284,11 +284,11 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s // Shield code if (session.getPlayerEntity().getEntityId() == entityId && metadata.getFlags().getFlag(EntityFlag.SNEAKING)) { - if ((session.getInventory().getItemInHand() != null && session.getInventory().getItemInHand().getId() == ItemRegistry.SHIELD) || - (session.getInventoryCache().getPlayerInventory().getItem(45) != null && session.getInventoryCache().getPlayerInventory().getItem(45).getId() == ItemRegistry.SHIELD)) { + if ((session.getInventory().getItemInHand() != null && session.getInventory().getItemInHand().getId() == ItemRegistry.SHIELD.getJavaId()) || + (session.getInventoryCache().getPlayerInventory().getItem(45) != null && session.getInventoryCache().getPlayerInventory().getItem(45).getId() == ItemRegistry.SHIELD.getJavaId())) { ClientPlayerUseItemPacket useItemPacket; metadata.getFlags().setFlag(EntityFlag.BLOCKING, true); - if (session.getInventory().getItemInHand() != null && session.getInventory().getItemInHand().getId() == ItemRegistry.SHIELD) { + if (session.getInventory().getItemInHand() != null && session.getInventory().getItemInHand().getId() == ItemRegistry.SHIELD.getJavaId()) { useItemPacket = new ClientPlayerUseItemPacket(Hand.MAIN_HAND); } // Else we just assume it's the offhand, to simplify logic and to assure the packet gets sent 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 ae9319efa9f..83027e30e42 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 @@ -61,7 +61,7 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s @Override public void updateEquipment(GeyserSession session) { // Check if the Piglin is holding Gold and set the ADMIRING flag accordingly - metadata.getFlags().setFlag(EntityFlag.ADMIRING, offHand.getId() == ItemRegistry.GOLD); + metadata.getFlags().setFlag(EntityFlag.ADMIRING, offHand.getId() == ItemRegistry.GOLD.getBedrockId()); super.updateBedrockMetadata(session); super.updateEquipment(session); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInteractTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInteractTranslator.java index 167073273a3..7d03f98328e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInteractTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInteractTranslator.java @@ -60,7 +60,7 @@ public void translate(InteractPacket packet, GeyserSession session) { switch (packet.getAction()) { case INTERACT: - if (session.getInventory().getItem(session.getInventory().getHeldItemSlot() + 36).getId() == ItemRegistry.SHIELD) { + if (session.getInventory().getItem(session.getInventory().getHeldItemSlot() + 36).getId() == ItemRegistry.SHIELD.getJavaId()) { break; } ClientPlayerInteractEntityPacket interactPacket = new ClientPlayerInteractEntityPacket((int) entity.getEntityId(), 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 25e1c54e2a5..d4168709fd9 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 @@ -99,7 +99,7 @@ public void translate(InventoryTransactionPacket packet, GeyserSession session) session.sendDownstreamPacket(blockPacket); // Otherwise boats will not be able to be placed in survival - if (packet.getItemInHand() != null && packet.getItemInHand().getId() == ItemRegistry.BOAT) { + if (packet.getItemInHand() != null && packet.getItemInHand().getId() == ItemRegistry.BOAT.getBedrockId()) { ClientPlayerUseItemPacket itemPacket = new ClientPlayerUseItemPacket(Hand.MAIN_HAND); session.sendDownstreamPacket(itemPacket); } @@ -136,7 +136,7 @@ public void translate(InventoryTransactionPacket packet, GeyserSession session) break; case 1: ItemStack shieldSlot = session.getInventory().getItem(session.getInventory().getHeldItemSlot() + 36); - if (shieldSlot != null && shieldSlot.getId() == ItemRegistry.SHIELD) { + if (shieldSlot != null && shieldSlot.getId() == ItemRegistry.SHIELD.getJavaId()) { break; } // Handled in Entity.java ClientPlayerUseItemPacket useItemPacket = new ClientPlayerUseItemPacket(Hand.MAIN_HAND); 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 9c072ad150e..19f83cd963b 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 @@ -27,9 +27,11 @@ import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.ToString; @Getter @AllArgsConstructor +@ToString public class ItemEntry { public static ItemEntry AIR = new ItemEntry("minecraft:air", 0, 0, 0, false); 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 b9ce5c6c111..8224a52d239 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 @@ -57,11 +57,11 @@ public class ItemRegistry { public static final Int2ObjectMap ITEM_ENTRIES = new Int2ObjectOpenHashMap<>(); // Shield ID, used in Entity.java - public static int SHIELD = 0; + public static ItemEntry SHIELD; // Boat ID, used in BedrockInventoryTransactionTranslator.java - public static int BOAT = 0; + public static ItemEntry BOAT; // Gold ID, used in PiglinEntity.java - public static int GOLD = 0; + public static ItemEntry GOLD; public static int BARRIER_INDEX = 0; @@ -85,20 +85,6 @@ public static void init() { for (JsonNode entry : itemEntries) { ITEMS.add(new StartGamePacket.ItemEntry(entry.get("name").textValue(), (short) entry.get("id").intValue())); - - switch (entry.get("name").textValue()) { - case "minecraft:shield": - SHIELD = entry.get("id").intValue(); - break; - case "minecraft:boat": - BOAT = entry.get("id").intValue(); - break; - case "minecraft:gold_ingot": - GOLD = entry.get("id").intValue(); - break; - default: - break; - } } stream = FileUtils.getResource("mappings/items.json"); @@ -139,8 +125,21 @@ public static void init() { entry.getValue().get("bedrock_data").intValue(), entry.getValue().get("is_block") != null && entry.getValue().get("is_block").booleanValue())); } - if (entry.getKey().equals("minecraft:barrier")) { - BARRIER_INDEX = itemIndex; + switch (entry.getKey()) { + case "minecraft:barrier": + BARRIER_INDEX = itemIndex; + break; + case "minecraft:oak_boat": + BOAT = ITEM_ENTRIES.get(itemIndex); + break; + case "minecraft:gold_ingot": + GOLD = ITEM_ENTRIES.get(itemIndex); + break; + case "minecraft:shield": + SHIELD = ITEM_ENTRIES.get(itemIndex); + break; + default: + break; } itemIndex++; From 40032987faf3d87785f31b232f4e0e797328613b Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Tue, 7 Jul 2020 19:27:12 -0400 Subject: [PATCH 66/72] Add magma cube jumping visual --- .../entity/living/MagmaCubeEntity.java | 65 +++++++++++++++++++ .../connector/entity/type/EntityType.java | 2 +- 2 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 connector/src/main/java/org/geysermc/connector/entity/living/MagmaCubeEntity.java diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/MagmaCubeEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/MagmaCubeEntity.java new file mode 100644 index 00000000000..320f08987e3 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/MagmaCubeEntity.java @@ -0,0 +1,65 @@ +/* + * 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.EntityData; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class MagmaCubeEntity extends SlimeEntity { + + public MagmaCubeEntity(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) { + super.updateBedrockMetadata(entityMetadata, session); + } + + @Override + public void moveRelative(GeyserSession session, double relX, double relY, double relZ, Vector3f rotation, boolean isOnGround) { + updateJump(session, isOnGround); + super.moveRelative(session, relX, relY, relZ, rotation, isOnGround); + } + + @Override + public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) { + updateJump(session, isOnGround); + super.moveAbsolute(session, position, rotation, isOnGround, teleported); + } + + public void updateJump(GeyserSession session, boolean newOnGround) { + if (newOnGround != onGround) { + // Add the jumping effect to the magma cube + metadata.put(EntityData.CLIENT_EVENT, (byte) (newOnGround ? 1 : 2)); + updateBedrockMetadata(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 6bc1a1b0bdc..e73fd7aa534 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 { SILVERFISH(MonsterEntity.class, 39, 0.3f, 0.4f), CAVE_SPIDER(MonsterEntity.class, 40, 0.5f, 0.7f), GHAST(FlyingEntity.class, 41, 4.0f), - MAGMA_CUBE(SlimeEntity.class, 42, 0.51f), + 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), WITCH(RaidParticipantEntity.class, 45, 1.8f, 0.6f, 0.6f, 1.62f), From 21ea1f2408b674793959be834f9b98115afc207e Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Tue, 7 Jul 2020 20:14:50 -0400 Subject: [PATCH 67/72] Update MCProtocolLib to fix #836 --- connector/pom.xml | 2 +- .../translators/java/JavaJoinGameTranslator.java | 16 +++++++--------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/connector/pom.xml b/connector/pom.xml index 3fdd5f37a96..9a202d6f5d2 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -105,7 +105,7 @@ com.github.steveice10 mcprotocollib - 7545884a2d + ed6e845095 compile 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 01f6b0b92e3..ca65c2b98cd 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 @@ -25,23 +25,21 @@ package org.geysermc.connector.network.translators.java; -import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; +import com.github.steveice10.mc.protocol.data.game.entity.player.HandPreference; import com.github.steveice10.mc.protocol.data.game.setting.ChatVisibility; import com.github.steveice10.mc.protocol.data.game.setting.SkinPart; import com.github.steveice10.mc.protocol.packet.ingame.client.ClientSettingsPacket; -import org.geysermc.connector.entity.PlayerEntity; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.session.cache.ScoreboardCache; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.utils.DimensionUtils; - import com.github.steveice10.mc.protocol.packet.ingame.server.ServerJoinGamePacket; import com.nukkitx.protocol.bedrock.data.PlayerPermission; import com.nukkitx.protocol.bedrock.packet.AdventureSettingsPacket; import com.nukkitx.protocol.bedrock.packet.PlayStatusPacket; import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket; import com.nukkitx.protocol.bedrock.packet.SetPlayerGameTypePacket; +import org.geysermc.connector.entity.PlayerEntity; +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.DimensionUtils; import java.util.Arrays; import java.util.List; @@ -87,7 +85,7 @@ public void translate(ServerJoinGamePacket packet, GeyserSession session) { // We need to send our skin parts to the server otherwise java sees us with no hat, jacket etc String locale = session.getClientData().getLanguageCode(); List skinParts = Arrays.asList(SkinPart.values()); - ClientSettingsPacket clientSettingsPacket = new ClientSettingsPacket(locale, (byte) session.getRenderDistance(), ChatVisibility.FULL, true, skinParts, Hand.MAIN_HAND); + ClientSettingsPacket clientSettingsPacket = new ClientSettingsPacket(locale, (byte) session.getRenderDistance(), ChatVisibility.FULL, true, skinParts, HandPreference.RIGHT_HAND); session.sendDownstreamPacket(clientSettingsPacket); if (!packet.getDimension().equals(entity.getDimension())) { From 915ad2d0573abfbf8a684d6b179d142ea3082c23 Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Wed, 8 Jul 2020 11:31:09 -0400 Subject: [PATCH 68/72] Update languages submodule --- connector/src/main/resources/languages | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/src/main/resources/languages b/connector/src/main/resources/languages index 37a10837d1c..3ab423197a4 160000 --- a/connector/src/main/resources/languages +++ b/connector/src/main/resources/languages @@ -1 +1 @@ -Subproject commit 37a10837d1cf34b4c28f4879b62fcef200d43e60 +Subproject commit 3ab423197a4a308665ad1dcb28cab522aabc0815 From fe254380dc2c068c91e66d00348d0bba1502036f Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Wed, 8 Jul 2020 12:36:26 -0400 Subject: [PATCH 69/72] Clean up some TODOs --- .../main/java/org/geysermc/connector/entity/Entity.java | 4 ++-- .../org/geysermc/connector/entity/FishingHookEntity.java | 2 -- .../org/geysermc/connector/entity/PaintingEntity.java | 2 +- .../connector/entity/living/monster/EndermanEntity.java | 2 +- .../org/geysermc/connector/entity/type/EntityType.java | 8 ++++---- .../java/org/geysermc/connector/utils/InventoryUtils.java | 2 +- .../java/org/geysermc/connector/utils/MessageUtils.java | 3 +-- 7 files changed, 10 insertions(+), 13 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 1618d6afa36..78d0cd97727 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/Entity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/Entity.java @@ -299,7 +299,7 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s } } else if (session.getPlayerEntity().getEntityId() == entityId && !metadata.getFlags().getFlag(EntityFlag.SNEAKING) && metadata.getFlags().getFlag(EntityFlag.BLOCKING)) { metadata.getFlags().setFlag(EntityFlag.BLOCKING, false); - metadata.getFlags().setFlag(EntityFlag.IS_AVOIDING_BLOCK, true); //TODO: CHECK + metadata.getFlags().setFlag(EntityFlag.IS_AVOIDING_BLOCK, true); ClientPlayerActionPacket releaseItemPacket = new ClientPlayerActionPacket(PlayerAction.RELEASE_USE_ITEM, new Position(0, 0, 0), BlockFace.DOWN); session.sendDownstreamPacket(releaseItemPacket); } @@ -337,7 +337,7 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s if (entityMetadata.getValue().equals(Pose.SLEEPING)) { metadata.getFlags().setFlag(EntityFlag.SLEEPING, true); // Has to be a byte or it does not work - metadata.put(EntityData.PLAYER_FLAGS, (byte) 2); //TODO: CHECK + metadata.put(EntityData.PLAYER_FLAGS, (byte) 2); if (entityId == session.getPlayerEntity().getEntityId()) { Vector3i lastInteractionPos = session.getLastInteractionPosition(); metadata.put(EntityData.BED_POSITION, lastInteractionPos); diff --git a/connector/src/main/java/org/geysermc/connector/entity/FishingHookEntity.java b/connector/src/main/java/org/geysermc/connector/entity/FishingHookEntity.java index 4d0eb202f24..2949b57359d 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/FishingHookEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/FishingHookEntity.java @@ -63,8 +63,6 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s } } - //TODO Is ID 8 needed? - super.updateBedrockMetadata(entityMetadata, session); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/PaintingEntity.java b/connector/src/main/java/org/geysermc/connector/entity/PaintingEntity.java index d509b41f69a..8db7554b2b9 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/PaintingEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/PaintingEntity.java @@ -52,7 +52,7 @@ public void spawnEntity(GeyserSession session) { AddPaintingPacket addPaintingPacket = new AddPaintingPacket(); addPaintingPacket.setUniqueEntityId(geyserId); addPaintingPacket.setRuntimeEntityId(geyserId); - addPaintingPacket.setMotive(paintingName.getBedrockName()); //TODO: This is what it's called now? + addPaintingPacket.setMotive(paintingName.getBedrockName()); addPaintingPacket.setPosition(fixOffset(true)); addPaintingPacket.setDirection(direction); session.sendUpstreamPacket(addPaintingPacket); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/EndermanEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/EndermanEntity.java index 914b208593e..dfca080419a 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/EndermanEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/EndermanEntity.java @@ -43,7 +43,7 @@ public EndermanEntity(long entityId, long geyserId, EntityType entityType, Vecto public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { // Held block if (entityMetadata.getId() == 15) { - metadata.put(EntityData.DISPLAY_ITEM, BlockTranslator.getBedrockBlockId((int) entityMetadata.getValue())); //TODO: Check + metadata.put(EntityData.CARRIED_BLOCK, BlockTranslator.getBedrockBlockId((int) entityMetadata.getValue())); } // 'Angry' - mouth open if (entityMetadata.getId() == 16) { 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 e73fd7aa534..e32de056885 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 @@ -151,10 +151,10 @@ public enum EntityType { PANDA(PandaEntity.class, 113, 1.25f, 1.125f, 1.825f), 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"), //TODO - update entity metadata - HOGLIN(AnimalEntity.class, 124, 1.4f, 1.3965f, 1.3965f, 0f, "minecraft:hoglin"), //TODO - ZOGLIN(ZoglinEntity.class, 126, 1.4f, 1.3965f, 1.3965f, 0f, "minecraft:zoglin"), //TODO - PIGLIN(PiglinEntity.class, 123, 1.95f, 0.6f, 0.6f, 0f, "minecraft:piglin"), //TODO + 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"), + 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"), /** * Item frames are handled differently since they are a block in Bedrock. diff --git a/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java b/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java index 325710c6d6a..c30f4518af9 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java @@ -114,7 +114,7 @@ public static void closeWindow(GeyserSession session, int windowId) { public static void updateCursor(GeyserSession session) { InventorySlotPacket cursorPacket = new InventorySlotPacket(); - cursorPacket.setContainerId(ContainerId.UI); //TODO: CHECK IF ACCURATE + cursorPacket.setContainerId(ContainerId.UI); cursorPacket.setSlot(0); cursorPacket.setItem(ItemTranslator.translateToBedrock(session, session.getInventory().getCursor())); session.sendUpstreamPacket(cursorPacket); 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 44536ccf602..90a21c34568 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java @@ -483,8 +483,7 @@ public static String toChatColor(TeamColor teamColor) { */ public static boolean isTooLong(String message, GeyserSession session) { if (message.length() > 256) { - // TODO: Add Geyser localization and translate this based on language - session.sendMessage("Your message is bigger than 256 characters (" + message.length() + ") so it has not been sent."); + session.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.chat.too_long", session.getClientData().getLanguageCode(), message.length())); return true; } From bfdc452e3ecd6513e8e47e837459016f97ee4c15 Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Wed, 8 Jul 2020 11:06:58 -0400 Subject: [PATCH 70/72] Add emote support for Bedrock-to-Bedrock players --- .../bedrock/BedrockEmoteTranslator.java | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockEmoteTranslator.java diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockEmoteTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockEmoteTranslator.java new file mode 100644 index 00000000000..3e183df2e11 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockEmoteTranslator.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.network.translators.bedrock; + +import com.nukkitx.protocol.bedrock.packet.EmotePacket; +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; + +@Translator(packet = EmotePacket.class) +public class BedrockEmoteTranslator extends PacketTranslator { + + @Override + public void translate(EmotePacket packet, GeyserSession session) { + long javaId = session.getPlayerEntity().getEntityId(); + for (GeyserSession otherSession : GeyserConnector.getInstance().getPlayers()) { + if (otherSession != session) { + packet.setRuntimeEntityId(otherSession.getEntityCache().getEntityByJavaId(javaId).getGeyserId()); + otherSession.sendUpstreamPacket(packet); + System.out.println(packet); + } + } + } +} From 9cc468cee9e02571cb4fde6ed9d8b89656a8dd50 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Wed, 8 Jul 2020 12:42:20 -0400 Subject: [PATCH 71/72] Remove debug string --- .../network/translators/bedrock/BedrockEmoteTranslator.java | 1 - 1 file changed, 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockEmoteTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockEmoteTranslator.java index 3e183df2e11..42a720c471f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockEmoteTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockEmoteTranslator.java @@ -42,7 +42,6 @@ public void translate(EmotePacket packet, GeyserSession session) { if (otherSession != session) { packet.setRuntimeEntityId(otherSession.getEntityCache().getEntityByJavaId(javaId).getGeyserId()); otherSession.sendUpstreamPacket(packet); - System.out.println(packet); } } } From cbb2586fba5a21bda372dfd78d19236981eefa65 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Wed, 8 Jul 2020 18:44:10 -0400 Subject: [PATCH 72/72] Don't throw a stack trace when detecting for GUI (#919) --- .../src/main/java/org/geysermc/connector/GeyserConnector.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 601421303f1..435c1eaad06 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -167,7 +167,9 @@ private GeyserConnector(PlatformType platformType, GeyserBootstrap bootstrap) { try { Class cls = Class.forName("org.geysermc.platform.standalone.GeyserStandaloneBootstrap"); isGui = (boolean) cls.getMethod("isUseGui").invoke(cls.cast(bootstrap)); - } catch (Exception e) { e.printStackTrace(); } + } catch (Exception e) { + logger.debug("Failed detecting if standalone is using a GUI; if this is a GeyserConnect instance this can be safely ignored."); + } } double completeTime = (System.currentTimeMillis() - startupTime) / 1000D;