From 35d8edd15e665878627915c4c37c80710b0fc9cf Mon Sep 17 00:00:00 2001 From: Tim203 Date: Wed, 29 Jul 2020 12:53:00 +0200 Subject: [PATCH 001/107] Floodgate 2.0 update --- .../geysermc/floodgate/util/BedrockData.java | 67 +++++++++++----- .../util/{DeviceOS.java => DeviceOs.java} | 22 ++++-- .../floodgate/util/EncryptionUtil.java | 6 +- .../geysermc/floodgate/util/InputMode.java | 49 ++++++++++++ .../geysermc/floodgate/util/LinkedPlayer.java | 76 +++++++++++++++++++ .../geysermc/floodgate/util/UiProfile.java | 46 +++++++++++ .../network/session/GeyserSession.java | 1 + .../session/auth/BedrockClientData.java | 26 ++----- 8 files changed, 244 insertions(+), 49 deletions(-) rename common/src/main/java/org/geysermc/floodgate/util/{DeviceOS.java => DeviceOs.java} (79%) create mode 100644 common/src/main/java/org/geysermc/floodgate/util/InputMode.java create mode 100644 common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java create mode 100644 common/src/main/java/org/geysermc/floodgate/util/UiProfile.java diff --git a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java index dc895a79d..44544291d 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java +++ b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java @@ -1,36 +1,53 @@ package org.geysermc.floodgate.util; +import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Getter; -import java.util.UUID; - -@AllArgsConstructor +/** + * This class contains the raw data send by Geyser to Floodgate or from Floodgate to Floodgate. + * This class is only used internally, and you should look at FloodgatePlayer instead + * (FloodgatePlayer is present in the common module in the Floodgate repo) + */ +@AllArgsConstructor(access = AccessLevel.PRIVATE) @Getter -public class BedrockData { - public static final int EXPECTED_LENGTH = 7; +public final class BedrockData { + public static final int EXPECTED_LENGTH = 9; public static final String FLOODGATE_IDENTIFIER = "Geyser-Floodgate"; - private String version; - private String username; - private String xuid; - private int deviceId; - private String languageCode; - private int inputMode; - private String ip; - private int dataLength; + private final String version; + private final String username; + private final String xuid; + private final int deviceOs; + private final String languageCode; + private final int uiProfile; + private final int inputMode; + private final String ip; + private final LinkedPlayer linkedPlayer; + private final int dataLength; - public BedrockData(String version, String username, String xuid, int deviceId, String languageCode, int inputMode, String ip) { - this(version, username, xuid, deviceId, languageCode, inputMode, ip, EXPECTED_LENGTH); + public BedrockData(String version, String username, String xuid, int deviceOs, + String languageCode, int uiProfile, int inputMode, String ip, + LinkedPlayer linkedPlayer) { + this(version, username, xuid, deviceOs, languageCode, + inputMode, uiProfile, ip, linkedPlayer, EXPECTED_LENGTH); + } + + public BedrockData(String version, String username, String xuid, int deviceOs, + String languageCode, int uiProfile, int inputMode, String ip) { + this(version, username, xuid, deviceOs, languageCode, uiProfile, inputMode, ip, null); } public static BedrockData fromString(String data) { String[] split = data.split("\0"); - if (split.length != EXPECTED_LENGTH) return null; + if (split.length != EXPECTED_LENGTH) return emptyData(split.length); + LinkedPlayer linkedPlayer = LinkedPlayer.fromString(split[8]); + // The format is the same as the order of the fields in this class return new BedrockData( - split[0], split[1], split[2], Integer.parseInt(split[3]), - split[4], Integer.parseInt(split[5]), split[6], split.length + split[0], split[1], split[2], Integer.parseInt(split[3]), split[4], + Integer.parseInt(split[5]), Integer.parseInt(split[6]), split[7], + linkedPlayer, split.length ); } @@ -40,7 +57,17 @@ public class BedrockData { @Override public String toString() { - return version +'\0'+ username +'\0'+ xuid +'\0'+ deviceId +'\0'+ languageCode +'\0'+ - inputMode +'\0'+ ip; + // The format is the same as the order of the fields in this class + return version + '\0' + username + '\0' + xuid + '\0' + deviceOs + '\0' + + languageCode + '\0' + uiProfile + '\0' + inputMode + '\0' + ip + '\0' + + (linkedPlayer != null ? linkedPlayer.toString() : "null"); + } + + public boolean hasPlayerLink() { + return linkedPlayer != null; + } + + private static BedrockData emptyData(int dataLength) { + return new BedrockData(null, null, null, -1, null, -1, -1, null, null, dataLength); } } diff --git a/common/src/main/java/org/geysermc/floodgate/util/DeviceOS.java b/common/src/main/java/org/geysermc/floodgate/util/DeviceOs.java similarity index 79% rename from common/src/main/java/org/geysermc/floodgate/util/DeviceOS.java rename to common/src/main/java/org/geysermc/floodgate/util/DeviceOs.java index 93d3c121e..da783982c 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/DeviceOS.java +++ b/common/src/main/java/org/geysermc/floodgate/util/DeviceOs.java @@ -26,9 +26,14 @@ package org.geysermc.floodgate.util; import com.fasterxml.jackson.annotation.JsonEnumDefaultValue; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; -public enum DeviceOS { - +/** + * The Operation Systems where Bedrock players can connect with + */ +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public enum DeviceOs { @JsonEnumDefaultValue UNKNOWN("Unknown"), ANDROID("Android"), @@ -46,15 +51,16 @@ public enum DeviceOS { XBOX_ONE("Xbox One"), WIN_PHONE("Windows Phone"); - private static final DeviceOS[] VALUES = values(); + private static final DeviceOs[] VALUES = values(); private final String displayName; - DeviceOS(final String displayName) { - this.displayName = displayName; - } - - public static DeviceOS getById(int id) { + /** + * Get the DeviceOs instance from the identifier. + * @param id the DeviceOs identifier + * @return The DeviceOs or {@link #UNKNOWN} if the DeviceOs wasn't found + */ + public static DeviceOs getById(int id) { return id < VALUES.length ? VALUES[id] : VALUES[0]; } diff --git a/common/src/main/java/org/geysermc/floodgate/util/EncryptionUtil.java b/common/src/main/java/org/geysermc/floodgate/util/EncryptionUtil.java index 881d01ba9..619c7011a 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/EncryptionUtil.java +++ b/common/src/main/java/org/geysermc/floodgate/util/EncryptionUtil.java @@ -12,7 +12,11 @@ import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.Base64; -public class EncryptionUtil { +/** + * The class which contains all the encryption and decryption method used in Geyser and Floodgate + * (for Floodgate data). This is only used internally and doesn't serve a purpose for anything else + */ +public final class EncryptionUtil { public static String encrypt(Key key, String data) throws IllegalBlockSizeException, InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException { KeyGenerator generator = KeyGenerator.getInstance("AES"); diff --git a/common/src/main/java/org/geysermc/floodgate/util/InputMode.java b/common/src/main/java/org/geysermc/floodgate/util/InputMode.java new file mode 100644 index 000000000..4dcaa8ab3 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/util/InputMode.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.floodgate.util; + +import com.fasterxml.jackson.annotation.JsonEnumDefaultValue; + +public enum InputMode { + @JsonEnumDefaultValue + UNKNOWN, + KEYBOARD_MOUSE, + TOUCH, // I guess Touch? + CONTROLLER, + VR; + + private static final InputMode[] VALUES = values(); + + /** + * Get the InputMode instance from the identifier. + * @param id the InputMode identifier + * @return The InputMode or {@link #UNKNOWN} if the DeviceOs wasn't found + */ + public static InputMode getById(int id) { + return VALUES.length > id ? VALUES[id] : VALUES[0]; + } +} \ No newline at end of file diff --git a/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java b/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java new file mode 100644 index 000000000..a930b013a --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.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.floodgate.util; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; + +import java.util.UUID; + +@Getter +public final class LinkedPlayer { + /** + * The Java username of the linked player + */ + private final String javaUsername; + /** + * The Java UUID of the linked player + */ + private final UUID javaUniqueId; + /** + * The UUID of the Bedrock player + */ + private final UUID bedrockId; + /** + * If the LinkedPlayer is send from a different platform. + * For example the LinkedPlayer is from Bungee but the data has been sent to the Bukkit server. + */ + @Setter(AccessLevel.PRIVATE) + private boolean fromDifferentPlatform = false; + + public LinkedPlayer(String javaUsername, UUID javaUniqueId, UUID bedrockId) { + this.javaUsername = javaUsername; + this.javaUniqueId = javaUniqueId; + this.bedrockId = bedrockId; + } + + static LinkedPlayer fromString(String data) { + if (data.length() == 4) return null; + String[] split = data.split(";"); + LinkedPlayer player = new LinkedPlayer( + split[0], UUID.fromString(split[1]), UUID.fromString(split[2]) + ); + player.setFromDifferentPlatform(true); + return player; + } + + @Override + public String toString() { + return javaUsername + ';' + javaUniqueId.toString() + ';' + bedrockId.toString(); + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/util/UiProfile.java b/common/src/main/java/org/geysermc/floodgate/util/UiProfile.java new file mode 100644 index 000000000..441e9202a --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/util/UiProfile.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.floodgate.util; + +import com.fasterxml.jackson.annotation.JsonEnumDefaultValue; + +public enum UiProfile { + @JsonEnumDefaultValue + CLASSIC, + POCKET; + + private static final UiProfile[] VALUES = values(); + + /** + * Get the UiProfile instance from the identifier. + * @param id the UiProfile identifier + * @return The UiProfile or {@link #CLASSIC} if the UiProfile wasn't found + */ + public static UiProfile getById(int id) { + return VALUES.length > id ? VALUES[id] : VALUES[0]; + } +} 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 b861f64c4..623bf16a9 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 @@ -327,6 +327,7 @@ public class GeyserSession implements CommandSender { authData.getXboxUUID(), clientData.getDeviceOS().ordinal(), clientData.getLanguageCode(), + clientData.getUiProfile().ordinal(), clientData.getCurrentInputMode().ordinal(), upstream.getSession().getAddress().getAddress().getHostAddress() )); diff --git a/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java b/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java index 6aeebbaa3..fe8c769e9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java @@ -1,16 +1,17 @@ package org.geysermc.connector.network.session.auth; -import com.fasterxml.jackson.annotation.JsonEnumDefaultValue; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; -import org.geysermc.floodgate.util.DeviceOS; +import org.geysermc.floodgate.util.DeviceOs; +import org.geysermc.floodgate.util.InputMode; +import org.geysermc.floodgate.util.UiProfile; import java.util.UUID; @JsonIgnoreProperties(ignoreUnknown = true) @Getter -public class BedrockClientData { +public final class BedrockClientData { @JsonProperty(value = "GameVersion") private String gameVersion; @JsonProperty(value = "ServerAddress") @@ -52,9 +53,9 @@ public class BedrockClientData { @JsonProperty(value = "DeviceModel") private String deviceModel; @JsonProperty(value = "DeviceOS") - private DeviceOS deviceOS; + private DeviceOs deviceOS; @JsonProperty(value = "UIProfile") - private UIProfile uiProfile; + private UiProfile uiProfile; @JsonProperty(value = "GuiScale") private int guiScale; @JsonProperty(value = "CurrentInputMode") @@ -78,19 +79,4 @@ public class BedrockClientData { private String skinColor; @JsonProperty(value = "ThirdPartyNameOnly") private boolean thirdPartyNameOnly; - - public enum UIProfile { - @JsonEnumDefaultValue - CLASSIC, - POCKET - } - - public enum InputMode { - @JsonEnumDefaultValue - UNKNOWN, - KEYBOARD_MOUSE, - TOUCH, // I guess Touch? - CONTROLLER, - VR - } } From d37113388ba649ecb0b9941c040fa94bb1ce6e1f Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Tue, 18 Aug 2020 11:39:29 -0400 Subject: [PATCH 002/107] Update to 1.16.2 --- .../src/main/java/org/geysermc/connector/dump/DumpInfo.java | 6 +++--- .../java/org/geysermc/connector/entity/FireworkEntity.java | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java b/connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java index 9d91cde6b..5aed562ec 100644 --- a/connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java +++ b/connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java @@ -36,7 +36,7 @@ import org.geysermc.connector.network.BedrockProtocol; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.utils.DockerCheck; import org.geysermc.connector.utils.FileUtils; -import org.geysermc.floodgate.util.DeviceOS; +import org.geysermc.floodgate.util.DeviceOs; import java.io.IOException; import java.net.InetAddress; @@ -54,7 +54,7 @@ public class DumpInfo { private final DumpInfo.VersionInfo versionInfo; private Properties gitInfo; private final GeyserConfiguration config; - private Object2IntMap userPlatforms; + private Object2IntMap userPlatforms; private RamInfo ramInfo; private final BootstrapDumpInfo bootstrapInfo; @@ -72,7 +72,7 @@ public class DumpInfo { this.userPlatforms = new Object2IntOpenHashMap(); for (GeyserSession session : GeyserConnector.getInstance().getPlayers()) { - DeviceOS device = session.getClientData().getDeviceOS(); + DeviceOs device = session.getClientData().getDeviceOS(); userPlatforms.put(device, userPlatforms.getOrDefault(device, 0) + 1); } 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 b940b87b9..3ca170436 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/FireworkEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/FireworkEntity.java @@ -40,7 +40,7 @@ import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.utils.FireworkColor; import org.geysermc.connector.utils.MathUtils; -import org.geysermc.floodgate.util.DeviceOS; +import org.geysermc.floodgate.util.DeviceOs; import java.util.ArrayList; import java.util.List; @@ -67,7 +67,7 @@ public class FireworkEntity extends Entity { // TODO: Remove once Mojang fixes bugs with fireworks crashing clients on these specific devices. // https://bugs.mojang.com/browse/MCPE-89115 - if (session.getClientData().getDeviceOS() == DeviceOS.XBOX_ONE || session.getClientData().getDeviceOS() == DeviceOS.ORBIS) { + if (session.getClientData().getDeviceOS() == DeviceOs.XBOX_ONE || session.getClientData().getDeviceOS() == DeviceOs.ORBIS) { return; } From ec87344a772254e04e381cc313789f6b142d6594 Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Wed, 19 Aug 2020 23:13:05 -0400 Subject: [PATCH 003/107] Initial skin support --- .../spigot/GeyserSpigotConfiguration.java | 2 +- .../platform/spigot/GeyserSpigotPlugin.java | 2 +- .../geysermc/floodgate/util/BedrockData.java | 20 ++++--- .../floodgate/util/EncryptionUtil.java | 11 +++- .../org/geysermc/floodgate/util/RawSkin.java | 56 +++++++++++++++++++ .../network/session/GeyserSession.java | 3 +- .../session/auth/BedrockClientData.java | 47 ++++++++++++++++ .../connector/utils/LoginEncryptionUtils.java | 4 +- 8 files changed, 129 insertions(+), 16 deletions(-) create mode 100644 common/src/main/java/org/geysermc/floodgate/util/RawSkin.java diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotConfiguration.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotConfiguration.java index 380f70376..8667a692b 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotConfiguration.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotConfiguration.java @@ -48,7 +48,7 @@ public class GeyserSpigotConfiguration extends GeyserJacksonConfiguration { private Path floodgateKey; public void loadFloodgate(GeyserSpigotPlugin plugin) { - Plugin floodgate = Bukkit.getPluginManager().getPlugin("floodgate-bukkit"); + Plugin floodgate = Bukkit.getPluginManager().getPlugin("floodgate-spigot"); floodgateKey = FloodgateKeyLoader.getKey(plugin.getGeyserLogger(), this, Paths.get(plugin.getDataFolder().toString(), plugin.getConfig().getString("floodgate-key-file", "public-key.pem")), floodgate, floodgate != null ? floodgate.getDataFolder().toPath() : null); } 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 496681d33..b0cc5e6c3 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 @@ -93,7 +93,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { this.geyserLogger = new GeyserSpigotLogger(getLogger(), geyserConfig.isDebugMode()); GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); - if (geyserConfig.getRemote().getAuthType().equals("floodgate") && Bukkit.getPluginManager().getPlugin("floodgate-bukkit") == null) { + if (geyserConfig.getRemote().getAuthType().equals("floodgate") && Bukkit.getPluginManager().getPlugin("floodgate-spigot") == null) { geyserLogger.severe(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.disabling")); this.getPluginLoader().disablePlugin(this); return; diff --git a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java index 3d80c0f9b..1f1b3bc88 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java +++ b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java @@ -51,19 +51,21 @@ public final class BedrockData { private final LinkedPlayer linkedPlayer; private final int dataLength; + private RawSkin skin; + public BedrockData(String version, String username, String xuid, int deviceOs, String languageCode, int uiProfile, int inputMode, String ip, - LinkedPlayer linkedPlayer) { + LinkedPlayer linkedPlayer, RawSkin skin) { this(version, username, xuid, deviceOs, languageCode, - inputMode, uiProfile, ip, linkedPlayer, EXPECTED_LENGTH); + inputMode, uiProfile, ip, linkedPlayer, EXPECTED_LENGTH, skin); } public BedrockData(String version, String username, String xuid, int deviceOs, - String languageCode, int uiProfile, int inputMode, String ip) { - this(version, username, xuid, deviceOs, languageCode, uiProfile, inputMode, ip, null); + String languageCode, int uiProfile, int inputMode, String ip, RawSkin skin) { + this(version, username, xuid, deviceOs, languageCode, uiProfile, inputMode, ip, null, skin); } - public static BedrockData fromString(String data) { + public static BedrockData fromString(String data, String skin) { String[] split = data.split("\0"); if (split.length != EXPECTED_LENGTH) return emptyData(split.length); @@ -72,12 +74,12 @@ public final class BedrockData { return new BedrockData( split[0], split[1], split[2], Integer.parseInt(split[3]), split[4], Integer.parseInt(split[5]), Integer.parseInt(split[6]), split[7], - linkedPlayer, split.length + linkedPlayer, split.length, RawSkin.parse(skin) ); } - public static BedrockData fromRawData(byte[] data) { - return fromString(new String(data)); + public static BedrockData fromRawData(byte[] data, String skin) { + return fromString(new String(data), skin); } @Override @@ -93,6 +95,6 @@ public final class BedrockData { } private static BedrockData emptyData(int dataLength) { - return new BedrockData(null, null, null, -1, null, -1, -1, null, null, dataLength); + return new BedrockData(null, null, null, -1, null, -1, -1, null, null, dataLength, null); } } diff --git a/common/src/main/java/org/geysermc/floodgate/util/EncryptionUtil.java b/common/src/main/java/org/geysermc/floodgate/util/EncryptionUtil.java index ccad8d1c2..2f3048bd9 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/EncryptionUtil.java +++ b/common/src/main/java/org/geysermc/floodgate/util/EncryptionUtil.java @@ -58,9 +58,14 @@ public final class EncryptionUtil { Base64.getEncoder().encodeToString(encryptedText); } + public static String encryptBedrockData(Key key, BedrockData data, boolean includeSkin) throws IllegalBlockSizeException, + InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException { + return encrypt(key, data.toString()) + (includeSkin ? data.getSkin() : ""); + } + public static String encryptBedrockData(Key key, BedrockData data) throws IllegalBlockSizeException, InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException { - return encrypt(key, data.toString()); + return encryptBedrockData(key, data, true); } public static byte[] decrypt(Key key, String encryptedData) throws IllegalBlockSizeException, @@ -80,9 +85,9 @@ public final class EncryptionUtil { return cipher.doFinal(Base64.getDecoder().decode(split[1])); } - public static BedrockData decryptBedrockData(Key key, String encryptedData) throws IllegalBlockSizeException, + public static BedrockData decryptBedrockData(Key key, String encryptedData, String skin) throws IllegalBlockSizeException, InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException { - return BedrockData.fromRawData(decrypt(key, encryptedData)); + return BedrockData.fromRawData(decrypt(key, encryptedData), skin); } @SuppressWarnings("unchecked") diff --git a/common/src/main/java/org/geysermc/floodgate/util/RawSkin.java b/common/src/main/java/org/geysermc/floodgate/util/RawSkin.java new file mode 100644 index 000000000..ba22b632a --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/util/RawSkin.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.floodgate.util; + +import lombok.AllArgsConstructor; + +import java.nio.charset.StandardCharsets; + +@AllArgsConstructor +public class RawSkin { + public int width; + public int height; + public byte[] data; + + private RawSkin() {} + + public static RawSkin parse(String data) { + if (data == null) return null; + String[] split = data.split(":"); + if (split.length != 3) return null; + + RawSkin skin = new RawSkin(); + skin.width = Integer.parseInt(split[0]); + skin.height = Integer.parseInt(split[1]); + skin.data = split[2].getBytes(StandardCharsets.UTF_8); + return skin; + } + + @Override + public String toString() { + return Integer.toString(width) + ':' + height + ':' + new String(data); + } +} 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 06c669c9d..e53dd03aa 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 @@ -371,7 +371,8 @@ public class GeyserSession implements CommandSender { clientData.getLanguageCode(), clientData.getUiProfile().ordinal(), clientData.getCurrentInputMode().ordinal(), - upstream.getSession().getAddress().getAddress().getHostAddress() + upstream.getSession().getAddress().getAddress().getHostAddress(), + clientData.getImage("Skin") )); } catch (Exception e) { connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.encrypt_fail"), e); diff --git a/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java b/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java index ad1fb2fb1..2a3e174ed 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java @@ -25,18 +25,25 @@ package org.geysermc.connector.network.session.auth; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; +import net.minidev.json.JSONObject; import org.geysermc.floodgate.util.DeviceOs; import org.geysermc.floodgate.util.InputMode; +import org.geysermc.floodgate.util.RawSkin; import org.geysermc.floodgate.util.UiProfile; +import java.util.Base64; import java.util.UUID; @JsonIgnoreProperties(ignoreUnknown = true) @Getter public final class BedrockClientData { + @JsonIgnore + private JSONObject jsonData; + @JsonProperty(value = "GameVersion") private String gameVersion; @JsonProperty(value = "ServerAddress") @@ -104,4 +111,44 @@ public final class BedrockClientData { private String skinColor; @JsonProperty(value = "ThirdPartyNameOnly") private boolean thirdPartyNameOnly; + + public void setJsonData(JSONObject data) { + if (this.jsonData != null && data != null) { + this.jsonData = data; + } + } + + /** + * Taken from https://github.com/NukkitX/Nukkit/blob/master/src/main/java/cn/nukkit/network/protocol/LoginPacket.java
+ * Internally only used for Skins, but can be used for Capes too + */ + public RawSkin getImage(String name) { + if (jsonData == null || !jsonData.containsKey(name + "Data")) return null; + byte[] image = Base64.getDecoder().decode(jsonData.getAsString(name + "Data")); + if (jsonData.containsKey(name + "ImageWidth") && jsonData.containsKey(name + "ImageHeight")) { + return new RawSkin( + (int) jsonData.getAsNumber(name + "ImageWidth"), + (int) jsonData.get(name + "ImageHeight"), + image + ); + } + return getLegacyImage(image); + } + + private static RawSkin getLegacyImage(byte[] imageData) { + if (imageData == null) return null; + // width * height * 4 (rgba) + switch (imageData.length) { + case 8192: + return new RawSkin(64, 32, imageData); + case 16384: + return new RawSkin(64, 64, imageData); + case 32768: + return new RawSkin(64, 128, imageData); + case 65536: + return new RawSkin(128, 128, imageData); + default: + throw new IllegalArgumentException("Unknown legacy skin size"); + } + } } 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 4bc997bdf..7e4989d6e 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java @@ -131,7 +131,9 @@ public class LoginEncryptionUtils { JWSObject clientJwt = JWSObject.parse(clientData); EncryptionUtils.verifyJwt(clientJwt, identityPublicKey); - session.setClientData(JSON_MAPPER.convertValue(JSON_MAPPER.readTree(clientJwt.getPayload().toBytes()), BedrockClientData.class)); + BedrockClientData data = JSON_MAPPER.convertValue(JSON_MAPPER.readTree(clientJwt.getPayload().toBytes()), BedrockClientData.class); + data.setJsonData(clientJwt.getPayload().toJSONObject()); + session.setClientData(data); if (EncryptionUtils.canUseEncryption()) { LoginEncryptionUtils.startEncryptionHandshake(session, identityPublicKey); From 29977605213682fd672449def0829e9246ebcb04 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sat, 12 Sep 2020 15:29:18 +0200 Subject: [PATCH 004/107] Switch Floodgate encryption from RSA to AES --- .../geysermc/floodgate/crypto/AesCipher.java | 95 ++++++++++ .../floodgate/crypto/AesKeyProducer.java | 56 ++++++ .../floodgate/crypto/FloodgateCipher.java | 165 ++++++++++++++++++ .../floodgate/crypto/KeyProducer.java | 41 +++++ .../geysermc/floodgate/util/BedrockData.java | 19 +- .../floodgate/util/EncryptionUtil.java | 80 --------- .../util/InvalidHeaderException.java | 41 +++++ .../geysermc/floodgate/util/LinkedPlayer.java | 10 +- .../geysermc/connector/GeyserConnector.java | 19 +- .../network/session/GeyserSession.java | 43 ++--- 10 files changed, 445 insertions(+), 124 deletions(-) create mode 100644 common/src/main/java/org/geysermc/floodgate/crypto/AesCipher.java create mode 100644 common/src/main/java/org/geysermc/floodgate/crypto/AesKeyProducer.java create mode 100644 common/src/main/java/org/geysermc/floodgate/crypto/FloodgateCipher.java create mode 100644 common/src/main/java/org/geysermc/floodgate/crypto/KeyProducer.java delete mode 100644 common/src/main/java/org/geysermc/floodgate/util/EncryptionUtil.java create mode 100644 common/src/main/java/org/geysermc/floodgate/util/InvalidHeaderException.java diff --git a/common/src/main/java/org/geysermc/floodgate/crypto/AesCipher.java b/common/src/main/java/org/geysermc/floodgate/crypto/AesCipher.java new file mode 100644 index 000000000..3e6fef01a --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/crypto/AesCipher.java @@ -0,0 +1,95 @@ +/* + * 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/Floodgate + * + */ + +package org.geysermc.floodgate.crypto; + +import org.geysermc.floodgate.util.InvalidHeaderException; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.spec.GCMParameterSpec; +import java.nio.ByteBuffer; +import java.security.Key; +import java.security.SecureRandom; + +public final class AesCipher implements FloodgateCipher { + private static final int IV_LENGTH = 12; + private static final int TAG_BIT_LENGTH = 128; + private static final String CIPHER_NAME = "AES/GCM/NoPadding"; + + private final SecureRandom secureRandom = new SecureRandom(); + private SecretKey secretKey; + + public void init(Key key) { + if (!"AES".equals(key.getAlgorithm())) { + throw new RuntimeException( + "Algorithm was expected to be AES, but got " + key.getAlgorithm() + ); + } + secretKey = (SecretKey) key; + } + + public byte[] encrypt(byte[] data) throws Exception { + Cipher cipher = Cipher.getInstance(CIPHER_NAME); + + byte[] iv = new byte[IV_LENGTH]; + secureRandom.nextBytes(iv); + + GCMParameterSpec spec = new GCMParameterSpec(TAG_BIT_LENGTH, iv); + cipher.init(Cipher.ENCRYPT_MODE, secretKey, spec); + byte[] cipherText = cipher.doFinal(data); + + return ByteBuffer.allocate(iv.length + cipherText.length + HEADER_LENGTH) + .put(IDENTIFIER).put(VERSION) // header + .put(iv) + .put(cipherText) + .array(); + } + + public byte[] decrypt(byte[] cipherTextWithIv) throws Exception { + HeaderResult pair = checkHeader(cipherTextWithIv); + if (pair.getVersion() != VERSION) { + throw new InvalidHeaderException( + "Expected version " + VERSION + ", got " + pair.getVersion() + ); + } + + Cipher cipher = Cipher.getInstance(CIPHER_NAME); + + int bufferLength = cipherTextWithIv.length - HEADER_LENGTH; + ByteBuffer buffer = ByteBuffer.wrap(cipherTextWithIv, HEADER_LENGTH, bufferLength); + + byte[] iv = new byte[IV_LENGTH]; + buffer.get(iv); + + byte[] cipherText = new byte[buffer.remaining()]; + buffer.get(cipherText); + + GCMParameterSpec spec = new GCMParameterSpec(TAG_BIT_LENGTH, iv); + cipher.init(Cipher.DECRYPT_MODE, secretKey, spec); + return cipher.doFinal(cipherText); + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/crypto/AesKeyProducer.java b/common/src/main/java/org/geysermc/floodgate/crypto/AesKeyProducer.java new file mode 100644 index 000000000..5217b4cf7 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/crypto/AesKeyProducer.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Floodgate + * + */ + +package org.geysermc.floodgate.crypto; + +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.security.SecureRandom; + +public final class AesKeyProducer implements KeyProducer { + public static int KEY_SIZE = 128; + + @Override + public SecretKey produce() { + try { + KeyGenerator keyGenerator = KeyGenerator.getInstance("AES"); + keyGenerator.init(KEY_SIZE, SecureRandom.getInstanceStrong()); + return keyGenerator.generateKey(); + } catch (Exception exception) { + throw new RuntimeException(exception); + } + } + + @Override + public SecretKey produceFrom(byte[] keyFileData) { + try { + return new SecretKeySpec(keyFileData, "AES"); + } catch (Exception exception) { + throw new RuntimeException(exception); + } + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/crypto/FloodgateCipher.java b/common/src/main/java/org/geysermc/floodgate/crypto/FloodgateCipher.java new file mode 100644 index 000000000..23e57fb64 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/crypto/FloodgateCipher.java @@ -0,0 +1,165 @@ +/* + * 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/Floodgate + * + */ + +package org.geysermc.floodgate.crypto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import org.geysermc.floodgate.util.InvalidHeaderException; + +import java.nio.charset.StandardCharsets; +import java.security.Key; + +/** + * Responsible for both encrypting and decrypting data + */ +public interface FloodgateCipher { + byte[] IDENTIFIER = "Floodgate".getBytes(StandardCharsets.UTF_8); + byte VERSION = 2; + + int HEADER_LENGTH = IDENTIFIER.length + 1; // one byte for version + + /** + * Initializes the instance by giving it the key it needs to encrypt or decrypt data + * + * @param key the key used to encrypt and decrypt data + */ + void init(Key key); + + /** + * Encrypts the given data using the Key provided in {@link #init(Key)} + * + * @param data the data to encrypt + * @return the encrypted data + * @throws Exception when the encryption failed + */ + byte[] encrypt(byte[] data) throws Exception; + + /** + * Encrypts data from a String.
+ * This method internally calls {@link #encrypt(byte[])} + * + * @param data the data to encrypt + * @return the encrypted data + * @throws Exception when the encryption failed + */ + default byte[] encryptFromString(String data) throws Exception { + return encrypt(data.getBytes(StandardCharsets.UTF_8)); + } + + /** + * Decrypts the given data using the Key provided in {@link #init(Key)} + * + * @param data the data to decrypt + * @return the decrypted data + * @throws Exception when the decrypting failed + */ + byte[] decrypt(byte[] data) throws Exception; + + /** + * Decrypts a byte[] and turn it into a String.
+ * This method internally calls {@link #decrypt(byte[])} + * and converts the returned byte[] into a String. + * + * @param data the data to encrypt + * @return the decrypted data in a UTF-8 String + * @throws Exception when the decrypting failed + */ + default String decryptToString(byte[] data) throws Exception { + byte[] decrypted = decrypt(data); + if (decrypted == null) { + return null; + } + return new String(decrypted, StandardCharsets.UTF_8); + } + + /** + * Decrypts a String.
+ * This method internally calls {@link #decrypt(byte[])} + * by converting the UTF-8 String into a byte[] + * + * @param data the data to decrypt + * @return the decrypted data in a byte[] + * @throws Exception when the decrypting failed + */ + default byte[] decryptFromString(String data) throws Exception { + return decrypt(data.getBytes(StandardCharsets.UTF_8)); + } + + /** + * Checks if the header is valid and return a IntPair containing the header version + * and the index to start reading the actual encrypted data from. + * + * @param data the data to check + * @return IntPair. x = version number, y = the index to start reading from. + * @throws InvalidHeaderException when the header is invalid + */ + default HeaderResult checkHeader(byte[] data) throws InvalidHeaderException { + final int identifierLength = IDENTIFIER.length; + + if (data.length <= HEADER_LENGTH) { + throw new InvalidHeaderException("Data length is smaller then header." + + "Needed " + HEADER_LENGTH + ", got " + data.length); + } + + for (int i = 0; i < identifierLength; i++) { + if (IDENTIFIER[i] != data[i]) { + StringBuilder receivedIdentifier = new StringBuilder(); + for (byte b : IDENTIFIER) { + receivedIdentifier.append(b); + } + + throw new InvalidHeaderException(String.format( + "Expected identifier %s, got %s", + new String(IDENTIFIER, StandardCharsets.UTF_8), + receivedIdentifier.toString() + )); + } + } + + return new HeaderResult(data[identifierLength], HEADER_LENGTH); + } + + static boolean hasHeader(String data) { + if (data.length() < IDENTIFIER.length) { + return false; + } + + for (int i = 0; i < IDENTIFIER.length; i++) { + if (IDENTIFIER[i] != data.charAt(i)) { + return false; + } + } + return true; + } + + @Data + @AllArgsConstructor + class HeaderResult { + private int version; + private int startIndex; + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/crypto/KeyProducer.java b/common/src/main/java/org/geysermc/floodgate/crypto/KeyProducer.java new file mode 100644 index 000000000..fc2ac512d --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/crypto/KeyProducer.java @@ -0,0 +1,41 @@ +/* + * 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/Floodgate + * + */ + +package org.geysermc.floodgate.crypto; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.Key; + +public interface KeyProducer { + Key produce(); + Key produceFrom(byte[] keyFileData); + + default Key produceFrom(Path keyFileLocation) throws IOException { + return produceFrom(Files.readAllBytes(keyFileLocation)); + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java index 44544291d..f730d75bd 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java +++ b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java @@ -9,11 +9,10 @@ import lombok.Getter; * This class is only used internally, and you should look at FloodgatePlayer instead * (FloodgatePlayer is present in the common module in the Floodgate repo) */ -@AllArgsConstructor(access = AccessLevel.PRIVATE) +@AllArgsConstructor(access = AccessLevel.PACKAGE) @Getter public final class BedrockData { public static final int EXPECTED_LENGTH = 9; - public static final String FLOODGATE_IDENTIFIER = "Geyser-Floodgate"; private final String version; private final String username; @@ -38,9 +37,15 @@ public final class BedrockData { this(version, username, xuid, deviceOs, languageCode, uiProfile, inputMode, ip, null); } + public boolean hasPlayerLink() { + return linkedPlayer != null; + } + public static BedrockData fromString(String data) { String[] split = data.split("\0"); - if (split.length != EXPECTED_LENGTH) return emptyData(split.length); + if (split.length != BedrockData.EXPECTED_LENGTH) { + return emptyData(split.length); + } LinkedPlayer linkedPlayer = LinkedPlayer.fromString(split[8]); // The format is the same as the order of the fields in this class @@ -51,10 +56,6 @@ public final class BedrockData { ); } - public static BedrockData fromRawData(byte[] data) { - return fromString(new String(data)); - } - @Override public String toString() { // The format is the same as the order of the fields in this class @@ -63,10 +64,6 @@ public final class BedrockData { (linkedPlayer != null ? linkedPlayer.toString() : "null"); } - public boolean hasPlayerLink() { - return linkedPlayer != null; - } - private static BedrockData emptyData(int dataLength) { return new BedrockData(null, null, null, -1, null, -1, -1, null, null, dataLength); } diff --git a/common/src/main/java/org/geysermc/floodgate/util/EncryptionUtil.java b/common/src/main/java/org/geysermc/floodgate/util/EncryptionUtil.java deleted file mode 100644 index 619c7011a..000000000 --- a/common/src/main/java/org/geysermc/floodgate/util/EncryptionUtil.java +++ /dev/null @@ -1,80 +0,0 @@ -package org.geysermc.floodgate.util; - -import javax.crypto.*; -import javax.crypto.spec.SecretKeySpec; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.security.*; -import java.security.spec.EncodedKeySpec; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.PKCS8EncodedKeySpec; -import java.security.spec.X509EncodedKeySpec; -import java.util.Base64; - -/** - * The class which contains all the encryption and decryption method used in Geyser and Floodgate - * (for Floodgate data). This is only used internally and doesn't serve a purpose for anything else - */ -public final class EncryptionUtil { - public static String encrypt(Key key, String data) throws IllegalBlockSizeException, - InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException { - KeyGenerator generator = KeyGenerator.getInstance("AES"); - generator.init(128); - SecretKey secretKey = generator.generateKey(); - - Cipher cipher = Cipher.getInstance("AES"); - cipher.init(Cipher.ENCRYPT_MODE, secretKey); - byte[] encryptedText = cipher.doFinal(data.getBytes()); - - cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); - cipher.init(key instanceof PublicKey ? Cipher.PUBLIC_KEY : Cipher.PRIVATE_KEY, key); - return Base64.getEncoder().encodeToString(cipher.doFinal(secretKey.getEncoded())) + '\0' + - Base64.getEncoder().encodeToString(encryptedText); - } - - public static String encryptBedrockData(Key key, BedrockData data) throws IllegalBlockSizeException, - InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException { - return encrypt(key, data.toString()); - } - - public static byte[] decrypt(Key key, String encryptedData) throws IllegalBlockSizeException, - InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException { - String[] split = encryptedData.split("\0"); - if (split.length != 2) { - throw new IllegalArgumentException("Expected two arguments, got " + split.length); - } - - Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); - cipher.init(key instanceof PublicKey ? Cipher.PUBLIC_KEY : Cipher.PRIVATE_KEY, key); - byte[] decryptedKey = cipher.doFinal(Base64.getDecoder().decode(split[0])); - - SecretKey secretKey = new SecretKeySpec(decryptedKey, 0, decryptedKey.length, "AES"); - cipher = Cipher.getInstance("AES"); - cipher.init(Cipher.DECRYPT_MODE, secretKey); - return cipher.doFinal(Base64.getDecoder().decode(split[1])); - } - - public static BedrockData decryptBedrockData(Key key, String encryptedData) throws IllegalBlockSizeException, - InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException { - return BedrockData.fromRawData(decrypt(key, encryptedData)); - } - - @SuppressWarnings("unchecked") - public static T getKeyFromFile(Path fileLocation, Class keyType) throws - IOException, InvalidKeySpecException, NoSuchAlgorithmException { - boolean isPublicKey = keyType == PublicKey.class; - if (!isPublicKey && keyType != PrivateKey.class) { - throw new RuntimeException("I can only read public and private keys!"); - } - - byte[] key = Files.readAllBytes(fileLocation); - - KeyFactory keyFactory = KeyFactory.getInstance("RSA"); - EncodedKeySpec keySpec = isPublicKey ? new X509EncodedKeySpec(key) : new PKCS8EncodedKeySpec(key); - return (T) (isPublicKey ? - keyFactory.generatePublic(keySpec) : - keyFactory.generatePrivate(keySpec) - ); - } -} diff --git a/common/src/main/java/org/geysermc/floodgate/util/InvalidHeaderException.java b/common/src/main/java/org/geysermc/floodgate/util/InvalidHeaderException.java new file mode 100644 index 000000000..30dbf0726 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/util/InvalidHeaderException.java @@ -0,0 +1,41 @@ +/* + * 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.floodgate.util; + +public class InvalidHeaderException extends Exception { + public InvalidHeaderException() { + super(); + } + + public InvalidHeaderException(String message) { + super(message); + } + + public InvalidHeaderException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java b/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java index a930b013a..53a167f9b 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java +++ b/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java @@ -26,9 +26,7 @@ package org.geysermc.floodgate.util; -import lombok.AccessLevel; import lombok.Getter; -import lombok.Setter; import java.util.UUID; @@ -50,7 +48,6 @@ public final class LinkedPlayer { * If the LinkedPlayer is send from a different platform. * For example the LinkedPlayer is from Bungee but the data has been sent to the Bukkit server. */ - @Setter(AccessLevel.PRIVATE) private boolean fromDifferentPlatform = false; public LinkedPlayer(String javaUsername, UUID javaUniqueId, UUID bedrockId) { @@ -60,12 +57,15 @@ public final class LinkedPlayer { } static LinkedPlayer fromString(String data) { - if (data.length() == 4) return null; + if (data.length() != 3) { + return null; + } + String[] split = data.split(";"); LinkedPlayer player = new LinkedPlayer( split[0], UUID.fromString(split[1]), UUID.fromString(split[2]) ); - player.setFromDifferentPlatform(true); + player.fromDifferentPlatform = true; return player; } diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index ee687dbbd..622add139 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -57,10 +57,14 @@ import org.geysermc.connector.utils.DimensionUtils; import org.geysermc.connector.utils.DockerCheck; import org.geysermc.connector.utils.LanguageUtils; import org.geysermc.connector.utils.LocaleUtils; +import org.geysermc.floodgate.crypto.AesCipher; +import org.geysermc.floodgate.crypto.AesKeyProducer; +import org.geysermc.floodgate.crypto.FloodgateCipher; import javax.naming.directory.Attribute; import javax.naming.directory.InitialDirContext; import java.net.InetSocketAddress; +import java.security.Key; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.List; @@ -77,7 +81,7 @@ public class GeyserConnector { public static final BedrockPacketCodec BEDROCK_PACKET_CODEC = Bedrock_v407.V407_CODEC; public static final String NAME = "Geyser"; - public static final String VERSION = "DEV"; // A fallback for running in IDEs + public static final String VERSION = "1.0.0 (git-master-35d8edd)"; // A fallback for running in IDEs private static final String IP_REGEX = "\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b"; @@ -89,6 +93,8 @@ public class GeyserConnector { @Setter private AuthType authType; + private FloodgateCipher cipher; + private boolean shuttingDown = false; private final ScheduledExecutorService generalThreadPool; @@ -165,6 +171,17 @@ public class GeyserConnector { remoteServer = new RemoteServer(config.getRemote().getAddress(), remotePort); authType = AuthType.getByName(config.getRemote().getAuthType()); + if (authType == AuthType.FLOODGATE) { + try { + Key key = new AesKeyProducer().produceFrom(config.getFloodgateKeyFile()); + cipher = new AesCipher(); + cipher.init(key); + logger.info(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.loaded_key")); + } catch (Exception exception) { + logger.severe(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.bad_key"), exception); + } + } + if (config.isAboveBedrockNetherBuilding()) DimensionUtils.changeBedrockNetherId(); // Apply End dimension ID workaround to Nether 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 623bf16a9..a02a356fb 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 @@ -71,15 +71,13 @@ import org.geysermc.connector.network.translators.PacketTranslatorRegistry; import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.utils.*; +import org.geysermc.floodgate.crypto.FloodgateCipher; import org.geysermc.floodgate.util.BedrockData; -import org.geysermc.floodgate.util.EncryptionUtil; -import java.io.IOException; import java.net.InetSocketAddress; -import java.security.NoSuchAlgorithmException; -import java.security.PublicKey; -import java.security.spec.InvalidKeySpecException; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Base64; import java.util.List; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; @@ -294,24 +292,6 @@ public class GeyserSession implements CommandSender { } boolean floodgate = connector.getAuthType() == AuthType.FLOODGATE; - final PublicKey publicKey; - - if (floodgate) { - PublicKey key = null; - try { - key = EncryptionUtil.getKeyFromFile( - connector.getConfig().getFloodgateKeyFile(), - PublicKey.class - ); - } catch (IOException | InvalidKeySpecException | NoSuchAlgorithmException e) { - connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.bad_key"), e); - } - publicKey = key; - } else publicKey = null; - - if (publicKey != null) { - connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.loaded_key")); - } downstream = new Client(remoteServer.getAddress(), remoteServer.getPort(), protocol, new TcpSessionFactory()); downstream.getSession().addListener(new SessionAdapter() { @@ -319,9 +299,11 @@ public class GeyserSession implements CommandSender { public void packetSending(PacketSendingEvent event) { //todo move this somewhere else if (event.getPacket() instanceof HandshakePacket && floodgate) { - String encrypted = ""; + byte[] encryptedData; + try { - encrypted = EncryptionUtil.encryptBedrockData(publicKey, new BedrockData( + FloodgateCipher cipher = connector.getCipher(); + encryptedData = cipher.encryptFromString(new BedrockData( clientData.getGameVersion(), authData.getName(), authData.getXboxUUID(), @@ -330,15 +312,22 @@ public class GeyserSession implements CommandSender { clientData.getUiProfile().ordinal(), clientData.getCurrentInputMode().ordinal(), upstream.getSession().getAddress().getAddress().getHostAddress() - )); + ).toString()); } catch (Exception e) { connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.encrypt_fail"), e); + disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.floodgate.encryption_fail", getClientData().getLanguageCode())); + return; } + String encrypted = new String( + Base64.getEncoder().encode(encryptedData), + StandardCharsets.UTF_8 + ); + HandshakePacket handshakePacket = event.getPacket(); event.setPacket(new HandshakePacket( handshakePacket.getProtocolVersion(), - handshakePacket.getHostname() + '\0' + BedrockData.FLOODGATE_IDENTIFIER + '\0' + encrypted, + handshakePacket.getHostname() + '\0' + encrypted, handshakePacket.getPort(), handshakePacket.getIntent() )); From 7fbc401dfa6b7841307be48eca457d699745e90e Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sat, 19 Sep 2020 14:21:54 +0200 Subject: [PATCH 005/107] Added RawSkins, Toppings and renamed the Floodgate plugin name --- .../bungeecord/GeyserBungeeConfiguration.java | 2 +- .../bungeecord/GeyserBungeePlugin.java | 2 +- .../spigot/GeyserSpigotConfiguration.java | 2 +- .../platform/spigot/GeyserSpigotPlugin.java | 2 +- .../geysermc/floodgate/crypto/AesCipher.java | 49 ++++++++++--- .../floodgate/crypto/Base64Topping.java | 40 +++++++++++ .../floodgate/crypto/FloodgateCipher.java | 35 +++++---- .../geysermc/floodgate/crypto/Topping.java | 31 ++++++++ .../geysermc/floodgate/util/BedrockData.java | 20 ++---- ...ption.java => InvalidFormatException.java} | 18 +++-- .../org/geysermc/floodgate/util/RawSkin.java | 55 ++++++++++---- .../geysermc/connector/GeyserConnector.java | 3 +- .../network/session/GeyserSession.java | 27 +++---- .../session/auth/BedrockClientData.java | 72 +++++++++++++------ .../connector/utils/LoginEncryptionUtils.java | 5 +- .../connector/utils/SkinProvider.java | 2 +- 16 files changed, 261 insertions(+), 104 deletions(-) create mode 100644 common/src/main/java/org/geysermc/floodgate/crypto/Base64Topping.java create mode 100644 common/src/main/java/org/geysermc/floodgate/crypto/Topping.java rename common/src/main/java/org/geysermc/floodgate/util/{InvalidHeaderException.java => InvalidFormatException.java} (77%) diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeConfiguration.java b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeConfiguration.java index d9b86a2e8..500f342a6 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeConfiguration.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeConfiguration.java @@ -44,7 +44,7 @@ public class GeyserBungeeConfiguration extends GeyserJacksonConfiguration { private Path floodgateKey; public void loadFloodgate(GeyserBungeePlugin plugin, Configuration configuration) { - Plugin floodgate = plugin.getProxy().getPluginManager().getPlugin("floodgate-bungee"); + Plugin floodgate = plugin.getProxy().getPluginManager().getPlugin("floodgate"); floodgateKey = FloodgateKeyLoader.getKey(plugin.getGeyserLogger(), this, Paths.get(plugin.getDataFolder().toString(), configuration.getString("floodgate-key-file"), "public-key.pem"), floodgate, floodgate != null ? floodgate.getDataFolder().toPath() : null); } 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 059e1dfd8..a600ebcd3 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 @@ -94,7 +94,7 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { this.geyserLogger = new GeyserBungeeLogger(getLogger(), geyserConfig.isDebugMode()); GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); - if (geyserConfig.getRemote().getAuthType().equals("floodgate") && getProxy().getPluginManager().getPlugin("floodgate-bungee") == null) { + if (geyserConfig.getRemote().getAuthType().equals("floodgate") && getProxy().getPluginManager().getPlugin("floodgate") == null) { geyserLogger.severe(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.disabling")); return; } diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotConfiguration.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotConfiguration.java index 8667a692b..de4e58c3b 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotConfiguration.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotConfiguration.java @@ -48,7 +48,7 @@ public class GeyserSpigotConfiguration extends GeyserJacksonConfiguration { private Path floodgateKey; public void loadFloodgate(GeyserSpigotPlugin plugin) { - Plugin floodgate = Bukkit.getPluginManager().getPlugin("floodgate-spigot"); + Plugin floodgate = Bukkit.getPluginManager().getPlugin("floodgate"); floodgateKey = FloodgateKeyLoader.getKey(plugin.getGeyserLogger(), this, Paths.get(plugin.getDataFolder().toString(), plugin.getConfig().getString("floodgate-key-file", "public-key.pem")), floodgate, floodgate != null ? floodgate.getDataFolder().toPath() : null); } 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 b0cc5e6c3..dab63c4ea 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 @@ -93,7 +93,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { this.geyserLogger = new GeyserSpigotLogger(getLogger(), geyserConfig.isDebugMode()); GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); - if (geyserConfig.getRemote().getAuthType().equals("floodgate") && Bukkit.getPluginManager().getPlugin("floodgate-spigot") == null) { + if (geyserConfig.getRemote().getAuthType().equals("floodgate") && Bukkit.getPluginManager().getPlugin("floodgate") == null) { geyserLogger.severe(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.disabling")); this.getPluginLoader().disablePlugin(this); return; diff --git a/common/src/main/java/org/geysermc/floodgate/crypto/AesCipher.java b/common/src/main/java/org/geysermc/floodgate/crypto/AesCipher.java index 3e6fef01a..2627584f6 100644 --- a/common/src/main/java/org/geysermc/floodgate/crypto/AesCipher.java +++ b/common/src/main/java/org/geysermc/floodgate/crypto/AesCipher.java @@ -26,7 +26,7 @@ package org.geysermc.floodgate.crypto; -import org.geysermc.floodgate.util.InvalidHeaderException; +import lombok.RequiredArgsConstructor; import javax.crypto.Cipher; import javax.crypto.SecretKey; @@ -35,12 +35,14 @@ import java.nio.ByteBuffer; import java.security.Key; import java.security.SecureRandom; +@RequiredArgsConstructor public final class AesCipher implements FloodgateCipher { - private static final int IV_LENGTH = 12; + public static final int IV_LENGTH = 12; private static final int TAG_BIT_LENGTH = 128; private static final String CIPHER_NAME = "AES/GCM/NoPadding"; private final SecureRandom secureRandom = new SecureRandom(); + private final Topping topping; private SecretKey secretKey; public void init(Key key) { @@ -62,32 +64,57 @@ public final class AesCipher implements FloodgateCipher { cipher.init(Cipher.ENCRYPT_MODE, secretKey, spec); byte[] cipherText = cipher.doFinal(data); - return ByteBuffer.allocate(iv.length + cipherText.length + HEADER_LENGTH) - .put(IDENTIFIER).put(VERSION) // header + if (topping != null) { + iv = topping.encode(iv); + cipherText = topping.encode(cipherText); + } + + return ByteBuffer.allocate(iv.length + cipherText.length + HEADER_LENGTH + 1) + .put(IDENTIFIER) // header .put(iv) + .put((byte) 0x21) .put(cipherText) .array(); } public byte[] decrypt(byte[] cipherTextWithIv) throws Exception { - HeaderResult pair = checkHeader(cipherTextWithIv); - if (pair.getVersion() != VERSION) { - throw new InvalidHeaderException( - "Expected version " + VERSION + ", got " + pair.getVersion() - ); - } + checkHeader(cipherTextWithIv); Cipher cipher = Cipher.getInstance(CIPHER_NAME); int bufferLength = cipherTextWithIv.length - HEADER_LENGTH; ByteBuffer buffer = ByteBuffer.wrap(cipherTextWithIv, HEADER_LENGTH, bufferLength); - byte[] iv = new byte[IV_LENGTH]; + int ivLength = IV_LENGTH; + + if (topping != null) { + int mark = buffer.position(); + + // we need the first index, the second is for the optional RawSkin + boolean found = false; + while (buffer.hasRemaining() && !found) { + if (buffer.get() == 0x21) { + found = true; + } + } + + ivLength = buffer.position() - mark - 1; // don't include the splitter itself + buffer.position(mark); // reset to the pre-while index + } + + byte[] iv = new byte[ivLength]; buffer.get(iv); + buffer.position(buffer.position() + 1); // skip splitter + byte[] cipherText = new byte[buffer.remaining()]; buffer.get(cipherText); + if (topping != null) { + iv = topping.decode(iv); + cipherText = topping.decode(cipherText); + } + GCMParameterSpec spec = new GCMParameterSpec(TAG_BIT_LENGTH, iv); cipher.init(Cipher.DECRYPT_MODE, secretKey, spec); return cipher.doFinal(cipherText); diff --git a/common/src/main/java/org/geysermc/floodgate/crypto/Base64Topping.java b/common/src/main/java/org/geysermc/floodgate/crypto/Base64Topping.java new file mode 100644 index 000000000..fbec78a1d --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/crypto/Base64Topping.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.floodgate.crypto; + +import java.util.Base64; + +public final class Base64Topping implements Topping { + @Override + public byte[] encode(byte[] data) { + return Base64.getEncoder().encode(data); + } + + @Override + public byte[] decode(byte[] data) { + return Base64.getDecoder().decode(data); + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/crypto/FloodgateCipher.java b/common/src/main/java/org/geysermc/floodgate/crypto/FloodgateCipher.java index 23e57fb64..4869531e2 100644 --- a/common/src/main/java/org/geysermc/floodgate/crypto/FloodgateCipher.java +++ b/common/src/main/java/org/geysermc/floodgate/crypto/FloodgateCipher.java @@ -28,7 +28,7 @@ package org.geysermc.floodgate.crypto; import lombok.AllArgsConstructor; import lombok.Data; -import org.geysermc.floodgate.util.InvalidHeaderException; +import org.geysermc.floodgate.util.InvalidFormatException; import java.nio.charset.StandardCharsets; import java.security.Key; @@ -38,9 +38,7 @@ import java.security.Key; */ public interface FloodgateCipher { byte[] IDENTIFIER = "Floodgate".getBytes(StandardCharsets.UTF_8); - byte VERSION = 2; - - int HEADER_LENGTH = IDENTIFIER.length + 1; // one byte for version + int HEADER_LENGTH = IDENTIFIER.length; /** * Initializes the instance by giving it the key it needs to encrypt or decrypt data @@ -110,19 +108,20 @@ public interface FloodgateCipher { } /** - * Checks if the header is valid and return a IntPair containing the header version - * and the index to start reading the actual encrypted data from. + * Checks if the header is valid. + * This method will throw an InvalidFormatException when the header is invalid. * * @param data the data to check - * @return IntPair. x = version number, y = the index to start reading from. - * @throws InvalidHeaderException when the header is invalid + * @throws InvalidFormatException when the header is invalid */ - default HeaderResult checkHeader(byte[] data) throws InvalidHeaderException { + default void checkHeader(byte[] data) throws InvalidFormatException { final int identifierLength = IDENTIFIER.length; if (data.length <= HEADER_LENGTH) { - throw new InvalidHeaderException("Data length is smaller then header." + - "Needed " + HEADER_LENGTH + ", got " + data.length); + throw new InvalidFormatException("Data length is smaller then header." + + "Needed " + HEADER_LENGTH + ", got " + data.length, + true + ); } for (int i = 0; i < identifierLength; i++) { @@ -132,15 +131,15 @@ public interface FloodgateCipher { receivedIdentifier.append(b); } - throw new InvalidHeaderException(String.format( - "Expected identifier %s, got %s", - new String(IDENTIFIER, StandardCharsets.UTF_8), - receivedIdentifier.toString() - )); + throw new InvalidFormatException( + String.format("Expected identifier %s, got %s", + new String(IDENTIFIER, StandardCharsets.UTF_8), + receivedIdentifier.toString() + ), + true + ); } } - - return new HeaderResult(data[identifierLength], HEADER_LENGTH); } static boolean hasHeader(String data) { diff --git a/common/src/main/java/org/geysermc/floodgate/crypto/Topping.java b/common/src/main/java/org/geysermc/floodgate/crypto/Topping.java new file mode 100644 index 000000000..f3dc0d4b8 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/crypto/Topping.java @@ -0,0 +1,31 @@ +/* + * 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.floodgate.crypto; + +public interface Topping { + byte[] encode(byte[] data); + byte[] decode(byte[] data); +} diff --git a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java index dcc412f50..89eaf5395 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java +++ b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java @@ -50,25 +50,23 @@ public final class BedrockData { private final LinkedPlayer linkedPlayer; private final int dataLength; - private RawSkin skin; - public BedrockData(String version, String username, String xuid, int deviceOs, String languageCode, int uiProfile, int inputMode, String ip, - LinkedPlayer linkedPlayer, RawSkin skin) { + LinkedPlayer linkedPlayer) { this(version, username, xuid, deviceOs, languageCode, - inputMode, uiProfile, ip, linkedPlayer, EXPECTED_LENGTH, skin); + inputMode, uiProfile, ip, linkedPlayer, EXPECTED_LENGTH); } public BedrockData(String version, String username, String xuid, int deviceOs, - String languageCode, int uiProfile, int inputMode, String ip, RawSkin skin) { - this(version, username, xuid, deviceOs, languageCode, uiProfile, inputMode, ip, null, skin); + String languageCode, int uiProfile, int inputMode, String ip) { + this(version, username, xuid, deviceOs, languageCode, uiProfile, inputMode, ip, null); } public boolean hasPlayerLink() { return linkedPlayer != null; } - public static BedrockData fromString(String data, String skin) { + public static BedrockData fromString(String data) { String[] split = data.split("\0"); if (split.length != EXPECTED_LENGTH) { return emptyData(split.length); @@ -79,14 +77,10 @@ public final class BedrockData { return new BedrockData( split[0], split[1], split[2], Integer.parseInt(split[3]), split[4], Integer.parseInt(split[5]), Integer.parseInt(split[6]), split[7], - linkedPlayer, split.length, RawSkin.parse(skin) + linkedPlayer, split.length ); } - public static BedrockData fromRawData(byte[] data, String skin) { - return fromString(new String(data), skin); - } - @Override public String toString() { // The format is the same as the order of the fields in this class @@ -96,6 +90,6 @@ public final class BedrockData { } private static BedrockData emptyData(int dataLength) { - return new BedrockData(null, null, null, -1, null, -1, -1, null, null, dataLength, null); + return new BedrockData(null, null, null, -1, null, -1, -1, null, null, dataLength); } } diff --git a/common/src/main/java/org/geysermc/floodgate/util/InvalidHeaderException.java b/common/src/main/java/org/geysermc/floodgate/util/InvalidFormatException.java similarity index 77% rename from common/src/main/java/org/geysermc/floodgate/util/InvalidHeaderException.java rename to common/src/main/java/org/geysermc/floodgate/util/InvalidFormatException.java index 30dbf0726..9ec5b1710 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/InvalidHeaderException.java +++ b/common/src/main/java/org/geysermc/floodgate/util/InvalidFormatException.java @@ -26,16 +26,26 @@ package org.geysermc.floodgate.util; -public class InvalidHeaderException extends Exception { - public InvalidHeaderException() { +import lombok.Getter; + +@Getter +public class InvalidFormatException extends Exception { + private boolean header = false; + + public InvalidFormatException() { super(); } - public InvalidHeaderException(String message) { + public InvalidFormatException(String message) { super(message); } - public InvalidHeaderException(String message, Throwable cause) { + public InvalidFormatException(String message, boolean header) { + super(message); + this.header = header; + } + + public InvalidFormatException(String message, Throwable cause) { super(message, cause); } } diff --git a/common/src/main/java/org/geysermc/floodgate/util/RawSkin.java b/common/src/main/java/org/geysermc/floodgate/util/RawSkin.java index ba22b632a..2ff0e3fb2 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/RawSkin.java +++ b/common/src/main/java/org/geysermc/floodgate/util/RawSkin.java @@ -26,31 +26,62 @@ package org.geysermc.floodgate.util; import lombok.AllArgsConstructor; +import lombok.ToString; -import java.nio.charset.StandardCharsets; +import java.nio.ByteBuffer; +import java.util.Base64; @AllArgsConstructor -public class RawSkin { +@ToString +public final class RawSkin { public int width; public int height; public byte[] data; private RawSkin() {} - public static RawSkin parse(String data) { - if (data == null) return null; - String[] split = data.split(":"); - if (split.length != 3) return null; + public static RawSkin decode(byte[] data) throws InvalidFormatException { + if (data == null) { + return null; + } + + int maxEncodedLength = 4 * (((64 * 64 * 4 + 8) + 2) / 3); + // if the RawSkin is longer then the max Java Edition skin length + if (data.length > maxEncodedLength) { + throw new InvalidFormatException( + "Encoded data cannot be longer then " + maxEncodedLength + " bytes!" + ); + } + + // if the encoded data doesn't even contain the width and height (8 bytes, 2 ints) + if (data.length < 4 * ((8 + 2) / 3)) { + throw new InvalidFormatException("Encoded data must be at least 12 bytes long!"); + } + + data = Base64.getDecoder().decode(data); + + ByteBuffer buffer = ByteBuffer.wrap(data); RawSkin skin = new RawSkin(); - skin.width = Integer.parseInt(split[0]); - skin.height = Integer.parseInt(split[1]); - skin.data = split[2].getBytes(StandardCharsets.UTF_8); + skin.width = buffer.getInt(); + skin.height = buffer.getInt(); + if (buffer.remaining() != (skin.width * skin.height * 4)) { + throw new InvalidFormatException(String.format( + "Expected skin length to be %s, got %s", + (skin.width * skin.height * 4), buffer.remaining() + )); + } + skin.data = new byte[buffer.remaining()]; + buffer.get(skin.data); return skin; } - @Override - public String toString() { - return Integer.toString(width) + ':' + height + ':' + new String(data); + public byte[] encode() { + // 2 x int = 8 bytes + ByteBuffer buffer = ByteBuffer.allocate(8 + data.length); + buffer.putInt(width); + buffer.putInt(height); + buffer.put(data); + return Base64.getEncoder().encode(buffer.array()); } } diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 21fb5564f..9b6045be2 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -57,6 +57,7 @@ import org.geysermc.connector.utils.LanguageUtils; import org.geysermc.connector.utils.LocaleUtils; import org.geysermc.floodgate.crypto.AesCipher; import org.geysermc.floodgate.crypto.AesKeyProducer; +import org.geysermc.floodgate.crypto.Base64Topping; import org.geysermc.floodgate.crypto.FloodgateCipher; import javax.naming.directory.Attribute; @@ -180,7 +181,7 @@ public class GeyserConnector { if (authType == AuthType.FLOODGATE) { try { Key key = new AesKeyProducer().produceFrom(config.getFloodgateKeyFile()); - cipher = new AesCipher(); + cipher = new AesCipher(new Base64Topping()); cipher.init(key); logger.info(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.loaded_key")); } catch (Exception exception) { 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 3e4314fd4..9a2ebe56d 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 @@ -77,15 +77,8 @@ import org.geysermc.floodgate.crypto.FloodgateCipher; import org.geysermc.floodgate.util.BedrockData; import java.net.InetSocketAddress; -import java.security.NoSuchAlgorithmException; -import java.security.PublicKey; -import java.security.spec.InvalidKeySpecException; -import java.util.*; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Base64; -import java.util.List; -import java.util.UUID; +import java.util.*; import java.util.concurrent.atomic.AtomicInteger; @Getter @@ -359,8 +352,7 @@ public class GeyserSession implements CommandSender { clientData.getLanguageCode(), clientData.getUiProfile().ordinal(), clientData.getCurrentInputMode().ordinal(), - upstream.getSession().getAddress().getAddress().getHostAddress(), - clientData.getImage("Skin") + upstream.getSession().getAddress().getAddress().getHostAddress() ).toString()); } catch (Exception e) { connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.encrypt_fail"), e); @@ -368,15 +360,18 @@ public class GeyserSession implements CommandSender { return; } - String encrypted = new String( - Base64.getEncoder().encode(encryptedData), - StandardCharsets.UTF_8 - ); + byte[] rawSkin = clientData.getAndTransformImage("Skin").encode(); + byte[] finalData = new byte[encryptedData.length + rawSkin.length + 1]; + System.arraycopy(encryptedData, 0, finalData, 0, encryptedData.length); + finalData[encryptedData.length] = 0x21; // splitter + System.arraycopy(rawSkin, 0, finalData, encryptedData.length + 1, rawSkin.length); + + String finalDataString = new String(finalData, StandardCharsets.UTF_8); HandshakePacket handshakePacket = event.getPacket(); event.setPacket(new HandshakePacket( handshakePacket.getProtocolVersion(), - handshakePacket.getHostname() + '\0' + encrypted, + handshakePacket.getHostname() + '\0' + finalDataString, handshakePacket.getPort(), handshakePacket.getIntent() )); @@ -626,7 +621,7 @@ public class GeyserSession implements CommandSender { /** * Send a packet immediately to the player. - * + * * @param packet the bedrock packet from the NukkitX protocol lib */ public void sendUpstreamPacketImmediately(BedrockPacket packet) { diff --git a/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java b/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java index 2a3e174ed..ab0fad845 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java @@ -28,13 +28,15 @@ package org.geysermc.connector.network.session.auth; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; import lombok.Getter; -import net.minidev.json.JSONObject; +import org.geysermc.connector.utils.SkinProvider; import org.geysermc.floodgate.util.DeviceOs; import org.geysermc.floodgate.util.InputMode; import org.geysermc.floodgate.util.RawSkin; import org.geysermc.floodgate.util.UiProfile; +import java.awt.image.BufferedImage; import java.util.Base64; import java.util.UUID; @@ -42,7 +44,7 @@ import java.util.UUID; @Getter public final class BedrockClientData { @JsonIgnore - private JSONObject jsonData; + private JsonNode jsonData; @JsonProperty(value = "GameVersion") private String gameVersion; @@ -112,31 +114,17 @@ public final class BedrockClientData { @JsonProperty(value = "ThirdPartyNameOnly") private boolean thirdPartyNameOnly; - public void setJsonData(JSONObject data) { - if (this.jsonData != null && data != null) { + public void setJsonData(JsonNode data) { + if (this.jsonData == null && data != null) { this.jsonData = data; } } - /** - * Taken from https://github.com/NukkitX/Nukkit/blob/master/src/main/java/cn/nukkit/network/protocol/LoginPacket.java
- * Internally only used for Skins, but can be used for Capes too - */ - public RawSkin getImage(String name) { - if (jsonData == null || !jsonData.containsKey(name + "Data")) return null; - byte[] image = Base64.getDecoder().decode(jsonData.getAsString(name + "Data")); - if (jsonData.containsKey(name + "ImageWidth") && jsonData.containsKey(name + "ImageHeight")) { - return new RawSkin( - (int) jsonData.getAsNumber(name + "ImageWidth"), - (int) jsonData.get(name + "ImageHeight"), - image - ); - } - return getLegacyImage(image); - } - private static RawSkin getLegacyImage(byte[] imageData) { - if (imageData == null) return null; + if (imageData == null) { + return null; + } + // width * height * 4 (rgba) switch (imageData.length) { case 8192: @@ -151,4 +139,44 @@ public final class BedrockClientData { throw new IllegalArgumentException("Unknown legacy skin size"); } } + + /** + * Taken from https://github.com/NukkitX/Nukkit/blob/master/src/main/java/cn/nukkit/network/protocol/LoginPacket.java
+ * Internally only used for Skins, but can be used for Capes too + */ + public RawSkin getImage(String name) { + System.out.println(jsonData.toString()); + if (jsonData == null || !jsonData.has(name + "Data")) { + return null; + } + + byte[] image = Base64.getDecoder().decode(jsonData.get(name + "Data").asText()); + if (jsonData.has(name + "ImageWidth") && jsonData.has(name + "ImageHeight")) { + return new RawSkin( + jsonData.get(name + "ImageWidth").asInt(), + jsonData.get(name + "ImageHeight").asInt(), + image + ); + } + return getLegacyImage(image); + } + + public RawSkin getAndTransformImage(String name) { + RawSkin skin = getImage(name); + if (skin != null && (skin.width > 64 || skin.height > 64)) { + BufferedImage scaledImage = SkinProvider.imageDataToBufferedImage(skin.data, skin.width, skin.height); + + int max = Math.max(skin.width, skin.height); + while (max > 64) { + max /= 2; + scaledImage = SkinProvider.scale(scaledImage); + } + + byte[] skinData = SkinProvider.bufferedImageToImageData(scaledImage); + skin.width = scaledImage.getWidth(); + skin.height = scaledImage.getHeight(); + skin.data = skinData; + } + return skin; + } } 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 7e4989d6e..62d70f612 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java @@ -131,8 +131,9 @@ public class LoginEncryptionUtils { JWSObject clientJwt = JWSObject.parse(clientData); EncryptionUtils.verifyJwt(clientJwt, identityPublicKey); - BedrockClientData data = JSON_MAPPER.convertValue(JSON_MAPPER.readTree(clientJwt.getPayload().toBytes()), BedrockClientData.class); - data.setJsonData(clientJwt.getPayload().toJSONObject()); + JsonNode clientDataJson = JSON_MAPPER.readTree(clientJwt.getPayload().toBytes()); + BedrockClientData data = JSON_MAPPER.convertValue(clientDataJson, BedrockClientData.class); + data.setJsonData(clientDataJson); session.setClientData(data); if (EncryptionUtils.canUseEncryption()) { 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 5551230b9..9b3d737e8 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java @@ -460,7 +460,7 @@ public class SkinProvider { return null; } - private static BufferedImage scale(BufferedImage bufferedImage) { + public static BufferedImage scale(BufferedImage bufferedImage) { BufferedImage resized = new BufferedImage(bufferedImage.getWidth() / 2, bufferedImage.getHeight() / 2, BufferedImage.TYPE_INT_ARGB); Graphics2D g2 = resized.createGraphics(); g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); From f8939ca5de6382b3f087aee9bc64e01bdbd76f27 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sat, 19 Sep 2020 14:45:36 +0200 Subject: [PATCH 006/107] Updated to latest Geyser --- .../src/main/java/org/geysermc/connector/GeyserConnector.java | 2 +- 1 file changed, 1 insertion(+), 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 de4d2c2bf..6cd6aa521 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -186,7 +186,7 @@ public class GeyserConnector { if (authType == AuthType.FLOODGATE) { try { - Key key = new AesKeyProducer().produceFrom(config.getFloodgateKeyFile()); + Key key = new AesKeyProducer().produceFrom(config.getFloodgateKeyPath()); cipher = new AesCipher(new Base64Topping()); cipher.init(key); logger.info(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.loaded_key")); From d75169392b0e35237a79f574df1e97c6d97a0759 Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Sun, 27 Sep 2020 21:08:31 -0400 Subject: [PATCH 007/107] Small consistency fixes --- .../org/geysermc/platform/bungeecord/GeyserBungeePlugin.java | 2 +- .../java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java | 2 +- .../main/java/org/geysermc/connector/FloodgateKeyLoader.java | 2 +- connector/src/main/resources/config.yml | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) 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 9d8e05ac7..e06640896 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 @@ -97,7 +97,7 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { if (geyserConfig.getRemote().getAuthType().equals("floodgate") && getProxy().getPluginManager().getPlugin("floodgate") == null) { geyserLogger.severe(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.disabling")); return; - } else if (geyserConfig.isAutoconfiguredRemote() && getProxy().getPluginManager().getPlugin("floodgate-bungee") != null) { + } else if (geyserConfig.isAutoconfiguredRemote() && getProxy().getPluginManager().getPlugin("floodgate") != null) { // Floodgate installed means that the user wants Floodgate authentication geyserLogger.debug("Auto-setting to Floodgate authentication."); geyserConfig.getRemote().setAuthType("floodgate"); 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 a8ae50d69..12794d1c8 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 @@ -101,7 +101,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { geyserLogger.severe(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.disabling")); this.getPluginLoader().disablePlugin(this); return; - } else if (geyserConfig.isAutoconfiguredRemote() && Bukkit.getPluginManager().getPlugin("floodgate-bukkit") != null) { + } else if (geyserConfig.isAutoconfiguredRemote() && Bukkit.getPluginManager().getPlugin("floodgate") != null) { // Floodgate installed means that the user wants Floodgate authentication geyserLogger.debug("Auto-setting to Floodgate authentication."); geyserConfig.getRemote().setAuthType("floodgate"); diff --git a/connector/src/main/java/org/geysermc/connector/FloodgateKeyLoader.java b/connector/src/main/java/org/geysermc/connector/FloodgateKeyLoader.java index ec5dd349a..7bf0ac67f 100644 --- a/connector/src/main/java/org/geysermc/connector/FloodgateKeyLoader.java +++ b/connector/src/main/java/org/geysermc/connector/FloodgateKeyLoader.java @@ -37,7 +37,7 @@ public class FloodgateKeyLoader { if (!Files.exists(floodgateKey) && config.getRemote().getAuthType().equals("floodgate")) { if (floodgate != null) { - Path autoKey = floodgateDataFolder.resolve("public-key.pem"); + Path autoKey = floodgateDataFolder.resolve("key.pem"); if (Files.exists(autoKey)) { logger.info(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.auto_loaded")); floodgateKey = autoKey; diff --git a/connector/src/main/resources/config.yml b/connector/src/main/resources/config.yml index 0602bb546..b9e6700fc 100644 --- a/connector/src/main/resources/config.yml +++ b/connector/src/main/resources/config.yml @@ -34,9 +34,9 @@ remote: auth-type: online # Floodgate uses encryption to ensure use from authorised sources. -# This should point to the public key generated by Floodgate (Bungee or CraftBukkit) +# This should point to the public key generated by Floodgate (BungeeCord, Spigot or Velocity) # You can ignore this when not using Floodgate. -floodgate-key-file: public-key.pem +floodgate-key-file: key.pem ## the Xbox/MCPE username is the key for the Java server auth-info ## this allows automatic configuration/login to the remote Java server From c9102348ded01f7fa058dcbbe983413a2b8dcd49 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Fri, 30 Oct 2020 00:58:17 +0100 Subject: [PATCH 008/107] First version of the Forms rewrite The next version will split the implementation from the api --- common/pom.xml | 2 +- .../org/geysermc/common/form/CustomForm.java | 177 ++++++++++++++++ .../java/org/geysermc/common/form/Form.java | 137 +++++++++++++ .../org/geysermc/common/form/ModalForm.java | 103 ++++++++++ .../org/geysermc/common/form/SimpleForm.java | 117 +++++++++++ .../component/ButtonComponent.java} | 37 ++-- .../common/form/component/Component.java | 77 +++++++ .../form/component/DropdownComponent.java | 102 +++++++++ .../component/InputComponent.java | 40 ++-- .../component/LabelComponent.java | 19 +- .../form/component/SliderComponent.java | 73 +++++++ .../form/component/StepSliderComponent.java | 116 +++++++++++ .../component/ToggleComponent.java | 35 ++-- .../form/response/CustomFormResponse.java | 193 ++++++++++++++++++ .../response/FormResponse.java | 8 +- .../response/ModalFormResponse.java} | 47 ++--- .../response/SimpleFormResponse.java} | 52 ++--- .../common/form/util/FormAdaptor.java | 126 ++++++++++++ .../button => form/util}/FormImage.java | 36 ++-- .../common/window/CustomFormBuilder.java | 71 ------- .../common/window/CustomFormWindow.java | 165 --------------- .../common/window/ModalFormWindow.java | 82 -------- .../common/window/SimpleFormWindow.java | 94 --------- .../window/component/DropdownComponent.java | 56 ----- .../window/component/FormComponent.java | 38 ---- .../window/component/SliderComponent.java | 65 ------ .../window/response/CustomFormResponse.java | 46 ----- .../window/response/FormResponseData.java | 37 ---- .../window/response/ModalFormResponse.java | 38 ---- .../window/response/SimpleFormResponse.java | 39 ---- .../network/UpstreamPacketHandler.java | 10 +- .../network/session/GeyserSession.java | 28 ++- .../{WindowCache.java => FormCache.java} | 70 +++---- ...edrockServerSettingsRequestTranslator.java | 8 +- .../java/JavaPluginMessageTranslator.java | 72 +++++-- .../connector/utils/LoginEncryptionUtils.java | 114 ++++------- .../connector/utils/SettingsUtils.java | 141 +++++-------- 37 files changed, 1559 insertions(+), 1112 deletions(-) create mode 100644 common/src/main/java/org/geysermc/common/form/CustomForm.java create mode 100644 common/src/main/java/org/geysermc/common/form/Form.java create mode 100644 common/src/main/java/org/geysermc/common/form/ModalForm.java create mode 100644 common/src/main/java/org/geysermc/common/form/SimpleForm.java rename common/src/main/java/org/geysermc/common/{window/button/FormButton.java => form/component/ButtonComponent.java} (65%) create mode 100644 common/src/main/java/org/geysermc/common/form/component/Component.java create mode 100644 common/src/main/java/org/geysermc/common/form/component/DropdownComponent.java rename common/src/main/java/org/geysermc/common/{window => form}/component/InputComponent.java (62%) rename common/src/main/java/org/geysermc/common/{window => form}/component/LabelComponent.java (81%) create mode 100644 common/src/main/java/org/geysermc/common/form/component/SliderComponent.java create mode 100644 common/src/main/java/org/geysermc/common/form/component/StepSliderComponent.java rename common/src/main/java/org/geysermc/common/{window => form}/component/ToggleComponent.java (70%) create mode 100644 common/src/main/java/org/geysermc/common/form/response/CustomFormResponse.java rename common/src/main/java/org/geysermc/common/{window => form}/response/FormResponse.java (87%) rename common/src/main/java/org/geysermc/common/{window/FormWindow.java => form/response/ModalFormResponse.java} (55%) rename common/src/main/java/org/geysermc/common/{window/component/StepSliderComponent.java => form/response/SimpleFormResponse.java} (56%) create mode 100644 common/src/main/java/org/geysermc/common/form/util/FormAdaptor.java rename common/src/main/java/org/geysermc/common/{window/button => form/util}/FormImage.java (72%) delete mode 100644 common/src/main/java/org/geysermc/common/window/CustomFormBuilder.java delete mode 100644 common/src/main/java/org/geysermc/common/window/CustomFormWindow.java delete mode 100644 common/src/main/java/org/geysermc/common/window/ModalFormWindow.java delete mode 100644 common/src/main/java/org/geysermc/common/window/SimpleFormWindow.java delete mode 100644 common/src/main/java/org/geysermc/common/window/component/DropdownComponent.java delete mode 100644 common/src/main/java/org/geysermc/common/window/component/FormComponent.java delete mode 100644 common/src/main/java/org/geysermc/common/window/component/SliderComponent.java delete mode 100644 common/src/main/java/org/geysermc/common/window/response/CustomFormResponse.java delete mode 100644 common/src/main/java/org/geysermc/common/window/response/FormResponseData.java delete mode 100644 common/src/main/java/org/geysermc/common/window/response/ModalFormResponse.java delete mode 100644 common/src/main/java/org/geysermc/common/window/response/SimpleFormResponse.java rename connector/src/main/java/org/geysermc/connector/network/session/cache/{WindowCache.java => FormCache.java} (56%) diff --git a/common/pom.xml b/common/pom.xml index 85dde12c6..30c8afc06 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -15,7 +15,7 @@ com.google.code.gson gson - 2.8.2 + 2.8.5 compile diff --git a/common/src/main/java/org/geysermc/common/form/CustomForm.java b/common/src/main/java/org/geysermc/common/form/CustomForm.java new file mode 100644 index 000000000..5e8d93252 --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/CustomForm.java @@ -0,0 +1,177 @@ +/* + * 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.common.form; + +import com.google.gson.annotations.JsonAdapter; +import lombok.Getter; +import org.geysermc.common.form.component.*; +import org.geysermc.common.form.response.CustomFormResponse; +import org.geysermc.common.form.util.FormAdaptor; +import org.geysermc.common.form.util.FormImage; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@Getter +@JsonAdapter(FormAdaptor.class) +public final class CustomForm extends Form { + private final String title; + private final FormImage icon; + private final List content; + + private CustomForm(String title, FormImage icon, List content) { + super(Form.Type.CUSTOM_FORM); + + this.title = title; + this.icon = icon; + this.content = Collections.unmodifiableList(content); + } + + public static Builder builder() { + return new Builder(); + } + + public static CustomForm of(String title, FormImage icon, List content) { + return new CustomForm(title, icon, content); + } + + public CustomFormResponse parseResponse(String data) { + if (isClosed(data)) { + return CustomFormResponse.closed(); + } + return CustomFormResponse.of(this, data); + } + + public static final class Builder extends Form.Builder { + private final List components = new ArrayList<>(); + private FormImage icon; + + public Builder icon(FormImage.Type type, String data) { + icon = FormImage.of(type, data); + return this; + } + + public Builder iconPath(String path) { + return icon(FormImage.Type.PATH, path); + } + + public Builder iconUrl(String url) { + return icon(FormImage.Type.URL, url); + } + + public Builder component(Component component) { + components.add(component); + return this; + } + + public Builder dropdown(DropdownComponent.Builder dropdownBuilder) { + return component(dropdownBuilder.translateAndBuild(this::translate)); + } + + public Builder dropdown(String text, int defaultOption, String... options) { + List optionsList = new ArrayList<>(); + for (String option : options) { + optionsList.add(translate(option)); + } + return component(DropdownComponent.of(translate(text), optionsList, defaultOption)); + } + + public Builder dropdown(String text, String... options) { + return dropdown(text, -1, options); + } + + public Builder input(String text, String placeholder, String defaultText) { + return component(InputComponent.of( + translate(text), translate(placeholder), translate(defaultText) + )); + } + + public Builder input(String text, String placeholder) { + return component(InputComponent.of(translate(text), translate(placeholder))); + } + + public Builder input(String text) { + return component(InputComponent.of(translate(text))); + } + + public Builder label(String text) { + return component(LabelComponent.of(translate(text))); + } + + public Builder slider(String text, float min, float max, int step, float defaultValue) { + return component(SliderComponent.of(text, min, max, step, defaultValue)); + } + + public Builder slider(String text, float min, float max, int step) { + return slider(text, min, max, step, -1); + } + + public Builder slider(String text, float min, float max, float defaultValue) { + return slider(text, min, max, -1, defaultValue); + } + + public Builder slider(String text, float min, float max) { + return slider(text, min, max, -1, -1); + } + + public Builder stepSlider(StepSliderComponent.Builder stepSliderBuilder) { + return component(stepSliderBuilder.translateAndBuild(this::translate)); + } + + public Builder stepSlider(String text, int defaultStep, String... steps) { + List stepsList = new ArrayList<>(); + for (String option : steps) { + stepsList.add(translate(option)); + } + return component(StepSliderComponent.of(translate(text), stepsList, defaultStep)); + } + + public Builder stepSlider(String text, String... steps) { + return stepSlider(text, -1, steps); + } + + public Builder toggle(String text, boolean defaultValue) { + return component(ToggleComponent.of(translate(text), defaultValue)); + } + + public Builder toggle(String text) { + return component(ToggleComponent.of(translate(text))); + } + + @Override + public CustomForm build() { + CustomForm form = of(title, icon, components); + if (biResponseHandler != null) { + form.setResponseHandler(response -> biResponseHandler.accept(form, response)); + return form; + } + + form.setResponseHandler(responseHandler); + return form; + } + } +} diff --git a/common/src/main/java/org/geysermc/common/form/Form.java b/common/src/main/java/org/geysermc/common/form/Form.java new file mode 100644 index 000000000..5b3437572 --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/Form.java @@ -0,0 +1,137 @@ +/* + * 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.common.form; + +import com.google.gson.Gson; +import com.google.gson.annotations.SerializedName; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import org.geysermc.common.form.response.FormResponse; + +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Consumer; + +@Getter +public abstract class Form { + protected static final Gson GSON = new Gson(); + private final Type type; + + @Getter(AccessLevel.NONE) + protected String hardcodedJsonData = null; + + @Setter protected Consumer responseHandler; + + public Form(Type type) { + this.type = type; + } + + public String getJsonData() { + if (hardcodedJsonData != null) { + return hardcodedJsonData; + } + return GSON.toJson(this); + } + + public abstract FormResponse parseResponse(String response); + + @SuppressWarnings("unchecked") + public T parseResponseAs(String response) { + return (T) parseResponse(response); + } + + public boolean isClosed(String response) { + return response == null || response.isEmpty() || response.equalsIgnoreCase("null"); + } + + @Getter + @RequiredArgsConstructor + public enum Type { + @SerializedName("form") + SIMPLE_FORM(SimpleForm.class), + @SerializedName("modal") + MODAL_FORM(ModalForm.class), + @SerializedName("custom_form") + CUSTOM_FORM(CustomForm.class); + + private static final Type[] VALUES = values(); + private final Class typeClass; + + public static Type getByOrdinal(int ordinal) { + return ordinal < VALUES.length ? VALUES[ordinal] : null; + } + } + + public static abstract class Builder, F extends Form> { + protected String title = ""; + + protected BiFunction translationHandler = null; + protected BiConsumer biResponseHandler; + protected Consumer responseHandler; + protected String locale; + + public T title(String title) { + this.title = translate(title); + return self(); + } + + public T translator(BiFunction translator, String locale) { + this.translationHandler = translator; + this.locale = locale; + return title(title); + } + + public T translator(BiFunction translator) { + return translator(translator, locale); + } + + public T responseHandler(BiConsumer responseHandler) { + biResponseHandler = responseHandler; + return self(); + } + + public T responseHandler(Consumer responseHandler) { + this.responseHandler = responseHandler; + return self(); + } + + public abstract F build(); + + protected String translate(String text) { + if (translationHandler != null && text != null && !text.isEmpty()) { + return translationHandler.apply(text, locale); + } + return text; + } + + @SuppressWarnings("unchecked") + protected T self() { + return (T) this; + } + } +} diff --git a/common/src/main/java/org/geysermc/common/form/ModalForm.java b/common/src/main/java/org/geysermc/common/form/ModalForm.java new file mode 100644 index 000000000..10309f9c2 --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/ModalForm.java @@ -0,0 +1,103 @@ +/* + * 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.common.form; + +import com.google.gson.annotations.JsonAdapter; +import lombok.Getter; +import org.geysermc.common.form.response.ModalFormResponse; +import org.geysermc.common.form.util.FormAdaptor; + +@Getter +@JsonAdapter(FormAdaptor.class) +public class ModalForm extends Form { + private final String title; + private final String content; + private final String button1; + private final String button2; + + private ModalForm(String title, String content, String button1, String button2) { + super(Type.MODAL_FORM); + + this.title = title; + this.content = content; + this.button1 = button1; + this.button2 = button2; + } + + public static Builder builder() { + return new Builder(); + } + + public static ModalForm of(String title, String content, String button1, String button2) { + return new ModalForm(title, content, button1, button2); + } + + public ModalFormResponse parseResponse(String data) { + if (isClosed(data)) { + return ModalFormResponse.closed(); + } + + if ("true".equals(data)) { + return ModalFormResponse.of(0, button1); + } else if ("false".equals(data)) { + return ModalFormResponse.of(1, button2); + } + return ModalFormResponse.invalid(); + } + + public static final class Builder extends Form.Builder { + private String content; + private String button1; + private String button2; + + public Builder content(String content) { + this.content = translate(content); + return this; + } + + public Builder button1(String button1) { + this.button1 = translate(button1); + return this; + } + + public Builder button2(String button2) { + this.button2 = translate(button2); + return this; + } + + @Override + public ModalForm build() { + ModalForm form = of(title, content, button1, button2); + if (biResponseHandler != null) { + form.setResponseHandler(response -> biResponseHandler.accept(form, response)); + return form; + } + + form.setResponseHandler(responseHandler); + return form; + } + } +} diff --git a/common/src/main/java/org/geysermc/common/form/SimpleForm.java b/common/src/main/java/org/geysermc/common/form/SimpleForm.java new file mode 100644 index 000000000..1a1ce616b --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/SimpleForm.java @@ -0,0 +1,117 @@ +/* + * 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.common.form; + +import com.google.gson.annotations.JsonAdapter; +import lombok.Getter; +import org.geysermc.common.form.component.ButtonComponent; +import org.geysermc.common.form.response.SimpleFormResponse; +import org.geysermc.common.form.util.FormAdaptor; +import org.geysermc.common.form.util.FormImage; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@Getter +@JsonAdapter(FormAdaptor.class) +public final class SimpleForm extends Form { + private final String title; + private final String content; + private final List buttons; + + private SimpleForm(String title, String content, List buttons) { + super(Type.SIMPLE_FORM); + + this.title = title; + this.content = content; + this.buttons = Collections.unmodifiableList(buttons); + } + + public static Builder builder() { + return new Builder(); + } + + public static SimpleForm of(String title, String content, List buttons) { + return new SimpleForm(title, content, buttons); + } + + public SimpleFormResponse parseResponse(String data) { + if (isClosed(data)) { + return SimpleFormResponse.closed(); + } + + int buttonId; + try { + buttonId = Integer.parseInt(data); + } catch (Exception exception) { + return SimpleFormResponse.invalid(); + } + + if (buttonId >= buttons.size()) { + return SimpleFormResponse.invalid(); + } + + return SimpleFormResponse.of(buttonId, buttons.get(buttonId)); + } + + public static final class Builder extends Form.Builder { + private final List buttons = new ArrayList<>(); + private String content; + + public Builder content(String content) { + this.content = translate(content); + return this; + } + + public Builder button(String text, FormImage.Type type, String data) { + buttons.add(ButtonComponent.of(translate(text), type, data)); + return this; + } + + public Builder button(String text, FormImage image) { + buttons.add(ButtonComponent.of(translate(text), image)); + return this; + } + + public Builder button(String text) { + buttons.add(ButtonComponent.of(translate(text))); + return this; + } + + @Override + public SimpleForm build() { + SimpleForm form = of(title, content, buttons); + if (biResponseHandler != null) { + form.setResponseHandler(response -> biResponseHandler.accept(form, response)); + return form; + } + + form.setResponseHandler(responseHandler); + return form; + } + } +} diff --git a/common/src/main/java/org/geysermc/common/window/button/FormButton.java b/common/src/main/java/org/geysermc/common/form/component/ButtonComponent.java similarity index 65% rename from common/src/main/java/org/geysermc/common/window/button/FormButton.java rename to common/src/main/java/org/geysermc/common/form/component/ButtonComponent.java index 6daa2feae..de241e3dd 100644 --- a/common/src/main/java/org/geysermc/common/window/button/FormButton.java +++ b/common/src/main/java/org/geysermc/common/form/component/ButtonComponent.java @@ -23,35 +23,28 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.common.window.button; +package org.geysermc.common.form.component; +import lombok.AccessLevel; import lombok.Getter; -import lombok.Setter; +import lombok.RequiredArgsConstructor; +import org.geysermc.common.form.util.FormImage; -public class FormButton { +@Getter +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public final class ButtonComponent { + private final String text; + private final FormImage image; - @Getter - @Setter - private String text; - - @Getter - private FormImage image; - - public FormButton(String text) { - this.text = text; + public static ButtonComponent of(String text, FormImage image) { + return new ButtonComponent(text, image); } - public FormButton(String text, FormImage image) { - this.text = text; - - if (image.getData() != null && !image.getData().isEmpty()) { - this.image = image; - } + public static ButtonComponent of(String text, FormImage.Type type, String data) { + return of(text, FormImage.of(type, data)); } - public void setImage(FormImage image) { - if (image.getData() != null && !image.getData().isEmpty()) { - this.image = image; - } + public static ButtonComponent of(String text) { + return of(text, null); } } diff --git a/common/src/main/java/org/geysermc/common/form/component/Component.java b/common/src/main/java/org/geysermc/common/form/component/Component.java new file mode 100644 index 000000000..8f44a2a0a --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/component/Component.java @@ -0,0 +1,77 @@ +/* + * 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.common.form.component; + +import com.google.gson.annotations.SerializedName; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Objects; + +@Getter +public abstract class Component { + private final Type type; + private final String text; + + Component(Type type, String text) { + Objects.requireNonNull(type, "Type cannot be null"); + Objects.requireNonNull(text, "Text cannot be null"); + + this.type = type; + this.text = text; + } + + @Getter + @RequiredArgsConstructor + public enum Type { + @SerializedName("dropdown") + DROPDOWN(DropdownComponent.class), + @SerializedName("input") + INPUT(InputComponent.class), + @SerializedName("label") + LABEL(LabelComponent.class), + @SerializedName("slider") + SLIDER(SliderComponent.class), + @SerializedName("step_slider") + STEP_SLIDER(StepSliderComponent.class), + @SerializedName("toggle") + TOGGLE(ToggleComponent.class); + + private static final Type[] VALUES = values(); + + private final String name = name().toLowerCase(); + private final Class componentClass; + + public static Type getByName(String name) { + for (Type type : VALUES) { + if (type.name.equals(name)) { + return type; + } + } + return null; + } + } +} diff --git a/common/src/main/java/org/geysermc/common/form/component/DropdownComponent.java b/common/src/main/java/org/geysermc/common/form/component/DropdownComponent.java new file mode 100644 index 000000000..6bf3f5e1a --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/component/DropdownComponent.java @@ -0,0 +1,102 @@ +/* + * 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.common.form.component; + +import com.google.gson.annotations.SerializedName; +import lombok.Getter; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.Function; + +@Getter +public class DropdownComponent extends Component { + private final List options; + @SerializedName("default") + private final int defaultOption; + + private DropdownComponent(String text, List options, int defaultOption) { + super(Type.DROPDOWN, text); + this.options = Collections.unmodifiableList(options); + this.defaultOption = defaultOption; + } + + public static DropdownComponent of(String text, List options, int defaultOption) { + if (defaultOption == -1 || defaultOption >= options.size()) { + defaultOption = 0; + } + return new DropdownComponent(text, options, defaultOption); + } + + public static Builder builder() { + return new Builder(); + } + + public static Builder builder(String text) { + return builder().text(text); + } + + public static class Builder { + private final List options = new ArrayList<>(); + private String text; + private int defaultOption = 0; + + public Builder text(String text) { + this.text = text; + return this; + } + + public Builder option(String option, boolean isDefault) { + options.add(option); + if (isDefault) { + defaultOption = options.size() - 1; + } + return this; + } + + public Builder option(String option) { + return option(option, false); + } + + public Builder defaultOption(int defaultOption) { + this.defaultOption = defaultOption; + return this; + } + + public DropdownComponent build() { + return of(text, options, defaultOption); + } + + public DropdownComponent translateAndBuild(Function translator) { + for (int i = 0; i < options.size(); i++) { + options.set(i, translator.apply(options.get(i))); + } + + return of(translator.apply(text), options, defaultOption); + } + } +} diff --git a/common/src/main/java/org/geysermc/common/window/component/InputComponent.java b/common/src/main/java/org/geysermc/common/form/component/InputComponent.java similarity index 62% rename from common/src/main/java/org/geysermc/common/window/component/InputComponent.java rename to common/src/main/java/org/geysermc/common/form/component/InputComponent.java index fad6a0fed..b5e07535f 100644 --- a/common/src/main/java/org/geysermc/common/window/component/InputComponent.java +++ b/common/src/main/java/org/geysermc/common/form/component/InputComponent.java @@ -23,30 +23,32 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.common.window.component; +package org.geysermc.common.form.component; +import com.google.gson.annotations.SerializedName; import lombok.Getter; -import lombok.Setter; -public class InputComponent extends FormComponent { +@Getter +public class InputComponent extends Component { + private final String placeholder; + @SerializedName("default") + private final String defaultText; - @Getter - @Setter - private String text; - - @Getter - @Setter - private String placeholder; - - @Getter - @Setter - private String defaultText; - - public InputComponent(String text, String placeholder, String defaultText) { - super("input"); - - this.text = text; + private InputComponent(String text, String placeholder, String defaultText) { + super(Type.INPUT, text); this.placeholder = placeholder; this.defaultText = defaultText; } + + public static InputComponent of(String text, String placeholder, String defaultText) { + return new InputComponent(text, placeholder, defaultText); + } + + public static InputComponent of(String text, String placeholder) { + return new InputComponent(text, placeholder, ""); + } + + public static InputComponent of(String text) { + return new InputComponent(text, "", ""); + } } diff --git a/common/src/main/java/org/geysermc/common/window/component/LabelComponent.java b/common/src/main/java/org/geysermc/common/form/component/LabelComponent.java similarity index 81% rename from common/src/main/java/org/geysermc/common/window/component/LabelComponent.java rename to common/src/main/java/org/geysermc/common/form/component/LabelComponent.java index a76b313fa..410f14a25 100644 --- a/common/src/main/java/org/geysermc/common/window/component/LabelComponent.java +++ b/common/src/main/java/org/geysermc/common/form/component/LabelComponent.java @@ -23,20 +23,17 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.common.window.component; +package org.geysermc.common.form.component; import lombok.Getter; -import lombok.Setter; -public class LabelComponent extends FormComponent { +@Getter +public class LabelComponent extends Component { + private LabelComponent(String text) { + super(Type.LABEL, text); + } - @Getter - @Setter - private String text; - - public LabelComponent(String text) { - super("label"); - - this.text = text; + public static LabelComponent of(String text) { + return new LabelComponent(text); } } diff --git a/common/src/main/java/org/geysermc/common/form/component/SliderComponent.java b/common/src/main/java/org/geysermc/common/form/component/SliderComponent.java new file mode 100644 index 000000000..0eb019e3a --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/component/SliderComponent.java @@ -0,0 +1,73 @@ +/* + * 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.common.form.component; + +import com.google.gson.annotations.SerializedName; +import lombok.Getter; + +@Getter +public final class SliderComponent extends Component { + private final float min; + private final float max; + private final int step; + @SerializedName("default") + private final float defaultValue; + + private SliderComponent(String text, float min, float max, int step, float defaultValue) { + super(Type.SLIDER, text); + this.min = min; + this.max = max; + this.step = step; + this.defaultValue = defaultValue; + } + + public static SliderComponent of(String text, float min, float max, int step, float defaultValue) { + min = Math.max(min, 0f); + max = Math.max(max, min); + + if (step < 1) { + step = 1; + } + + if (defaultValue == -1f) { + defaultValue = (int) Math.floor(min + max / 2D); + } + + return new SliderComponent(text, min, max, step, defaultValue); + } + + public static SliderComponent of(String text, float min, float max, int step) { + return of(text, min, max, step, -1); + } + + public static SliderComponent of(String text, float min, float max, float defaultValue) { + return of(text, min, max, -1, defaultValue); + } + + public static SliderComponent of(String text, float min, float max) { + return of(text, min, max, -1, -1); + } +} diff --git a/common/src/main/java/org/geysermc/common/form/component/StepSliderComponent.java b/common/src/main/java/org/geysermc/common/form/component/StepSliderComponent.java new file mode 100644 index 000000000..4d6d8fcb2 --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/component/StepSliderComponent.java @@ -0,0 +1,116 @@ +/* + * 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.common.form.component; + +import com.google.gson.annotations.SerializedName; +import lombok.Getter; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.function.Function; + +@Getter +public final class StepSliderComponent extends Component { + private final List steps; + @SerializedName("default") + private final int defaultStep; + + private StepSliderComponent(String text, List steps, int defaultStep) { + super(Type.STEP_SLIDER, text); + this.steps = Collections.unmodifiableList(steps); + this.defaultStep = defaultStep; + } + + public static StepSliderComponent of(String text, List steps, int defaultStep) { + if (text == null) { + text = ""; + } + + if (defaultStep >= steps.size() || defaultStep == -1) { + defaultStep = 0; + } + + return new StepSliderComponent(text, steps, defaultStep); + } + + public static StepSliderComponent of(String text, int defaultStep, String... steps) { + return of(text, Arrays.asList(steps), defaultStep); + } + + public static StepSliderComponent of(String text, String... steps) { + return of(text, 0, steps); + } + + public static Builder builder() { + return new Builder(); + } + + public static Builder builder(String text) { + return builder().text(text); + } + + public static final class Builder { + private final List steps = new ArrayList<>(); + private String text; + private int defaultStep; + + public Builder text(String text) { + this.text = text; + return this; + } + + public Builder step(String step, boolean defaultStep) { + steps.add(step); + if (defaultStep) { + this.defaultStep = steps.size() - 1; + } + return this; + } + + public Builder step(String step) { + return step(step, false); + } + + public Builder defaultStep(int defaultStep) { + this.defaultStep = defaultStep; + return this; + } + + public StepSliderComponent build() { + return of(text, steps, defaultStep); + } + + public StepSliderComponent translateAndBuild(Function translator) { + for (int i = 0; i < steps.size(); i++) { + steps.set(i, translator.apply(steps.get(i))); + } + + return of(translator.apply(text), steps, defaultStep); + } + } +} diff --git a/common/src/main/java/org/geysermc/common/window/component/ToggleComponent.java b/common/src/main/java/org/geysermc/common/form/component/ToggleComponent.java similarity index 70% rename from common/src/main/java/org/geysermc/common/window/component/ToggleComponent.java rename to common/src/main/java/org/geysermc/common/form/component/ToggleComponent.java index f972d5906..08341aaca 100644 --- a/common/src/main/java/org/geysermc/common/window/component/ToggleComponent.java +++ b/common/src/main/java/org/geysermc/common/form/component/ToggleComponent.java @@ -23,29 +23,26 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.common.window.component; +package org.geysermc.common.form.component; +import com.google.gson.annotations.SerializedName; import lombok.Getter; -import lombok.Setter; -public class ToggleComponent extends FormComponent { +@Getter +public class ToggleComponent extends Component { + @SerializedName("default") + private final boolean defaultValue; - @Getter - @Setter - private String text; - - @Getter - @Setter - private boolean defaultValue; - - public ToggleComponent(String text) { - this(text, false); - } - - public ToggleComponent(String text, boolean defaultValue) { - super("toggle"); - - this.text = text; + private ToggleComponent(String text, boolean defaultValue) { + super(Type.TOGGLE, text); this.defaultValue = defaultValue; } + + public static ToggleComponent of(String text, boolean defaultValue) { + return new ToggleComponent(text, defaultValue); + } + + public static ToggleComponent of(String text) { + return of(text, false); + } } diff --git a/common/src/main/java/org/geysermc/common/form/response/CustomFormResponse.java b/common/src/main/java/org/geysermc/common/form/response/CustomFormResponse.java new file mode 100644 index 000000000..8ef50c7fb --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/response/CustomFormResponse.java @@ -0,0 +1,193 @@ +/* + * 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.common.form.response; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonPrimitive; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.ToString; +import org.geysermc.common.form.CustomForm; +import org.geysermc.common.form.component.Component; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@Getter +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +@ToString +public class CustomFormResponse implements FormResponse { + private static final Gson GSON = new Gson(); + private static final CustomFormResponse CLOSED = + new CustomFormResponse(true, false, null, null); + private static final CustomFormResponse INVALID = + new CustomFormResponse(false, true, null, null); + private final boolean closed; + private final boolean invalid; + + private final JsonArray responses; + private final List componentTypes; + + private int index = -1; + + public static CustomFormResponse closed() { + return CLOSED; + } + + public static CustomFormResponse invalid() { + return INVALID; + } + + public static CustomFormResponse of(CustomForm form, String responseData) { + JsonArray responses = GSON.fromJson(responseData, JsonArray.class); + List types = new ArrayList<>(); + for (Component component : form.getContent()) { + types.add(component.getType()); + } + return of(types, responses); + } + + public static CustomFormResponse of(List componentTypes, + JsonArray responses) { + if (componentTypes.size() != responses.size()) { + return invalid(); + } + + return new CustomFormResponse(false, false, responses, + Collections.unmodifiableList(componentTypes)); + } + + @SuppressWarnings("unchecked") + public T next(boolean includeLabels) { + if (!hasNext()) { + return null; + } + + while (++index < responses.size()) { + Component.Type type = componentTypes.get(index); + if (type == Component.Type.LABEL && !includeLabels) { + continue; + } + return (T) getDataFromType(type, index); + } + return null; // we don't have anything to check anymore + } + + public T next() { + return next(false); + } + + public void skip(int amount) { + index += amount; + } + + public void skip() { + skip(1); + } + + public void index(int index) { + this.index = index; + } + + public boolean hasNext() { + return responses.size() > index + 1; + } + + public JsonPrimitive get(int index) { + try { + return responses.get(index).getAsJsonPrimitive(); + } catch (IllegalStateException exception) { + wrongType(index, "a primitive"); + return null; + } + } + + public int getDropdown(int index) { + JsonPrimitive primitive = get(index); + if (!primitive.isNumber()) { + wrongType(index, "dropdown"); + } + return primitive.getAsInt(); + } + + public String getInput(int index) { + JsonPrimitive primitive = get(index); + if (!primitive.isString()) { + wrongType(index, "input"); + } + return primitive.getAsString(); + } + + public float getSlider(int index) { + JsonPrimitive primitive = get(index); + if (!primitive.isNumber()) { + wrongType(index, "slider"); + } + return primitive.getAsFloat(); + } + + public int getStepSlide(int index) { + JsonPrimitive primitive = get(index); + if (!primitive.isNumber()) { + wrongType(index, "step slider"); + } + return primitive.getAsInt(); + } + + public boolean getToggle(int index) { + JsonPrimitive primitive = get(index); + if (!primitive.isBoolean()) { + wrongType(index, "toggle"); + } + return primitive.getAsBoolean(); + } + + private Object getDataFromType(Component.Type type, int index) { + switch (type) { + case DROPDOWN: + return getDropdown(index); + case INPUT: + return getInput(index); + case SLIDER: + return getSlider(index); + case STEP_SLIDER: + return getStepSlide(index); + case TOGGLE: + return getToggle(index); + default: + return null; // label e.g. is always null + } + } + + private void wrongType(int index, String expected) { + throw new IllegalStateException(String.format( + "Expected %s on %s, got %s", + expected, index, responses.get(index).toString())); + } +} diff --git a/common/src/main/java/org/geysermc/common/window/response/FormResponse.java b/common/src/main/java/org/geysermc/common/form/response/FormResponse.java similarity index 87% rename from common/src/main/java/org/geysermc/common/window/response/FormResponse.java rename to common/src/main/java/org/geysermc/common/form/response/FormResponse.java index 2be646837..7c62236f6 100644 --- a/common/src/main/java/org/geysermc/common/window/response/FormResponse.java +++ b/common/src/main/java/org/geysermc/common/form/response/FormResponse.java @@ -23,7 +23,13 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.common.window.response; +package org.geysermc.common.form.response; public interface FormResponse { + boolean isClosed(); + boolean isInvalid(); + + default boolean isCorrect() { + return !isClosed() && !isInvalid(); + } } diff --git a/common/src/main/java/org/geysermc/common/window/FormWindow.java b/common/src/main/java/org/geysermc/common/form/response/ModalFormResponse.java similarity index 55% rename from common/src/main/java/org/geysermc/common/window/FormWindow.java rename to common/src/main/java/org/geysermc/common/form/response/ModalFormResponse.java index c3cc4258b..b0e4de883 100644 --- a/common/src/main/java/org/geysermc/common/window/FormWindow.java +++ b/common/src/main/java/org/geysermc/common/form/response/ModalFormResponse.java @@ -23,37 +23,38 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.common.window; +package org.geysermc.common.form.response; -import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.AccessLevel; import lombok.Getter; -import lombok.Setter; -import org.geysermc.common.window.response.FormResponse; +import lombok.RequiredArgsConstructor; -public abstract class FormWindow { +@Getter +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public final class ModalFormResponse implements FormResponse { + private static final ModalFormResponse CLOSED = + new ModalFormResponse(true, false, -1, null); + private static final ModalFormResponse INVALID = + new ModalFormResponse(false, true, -1, null); + private final boolean closed; + private final boolean invalid; - @Getter - private final String type; + private final int clickedButtonId; + private final String clickedButtonText; - @Getter - protected FormResponse response; - - @Getter - @Setter - protected boolean closed; - - public FormWindow(String type) { - this.type = type; + public static ModalFormResponse closed() { + return CLOSED; } - // Lombok won't work here, so we need to make our own method - public void setResponse(FormResponse response) { - this.response = response; + public static ModalFormResponse invalid() { + return INVALID; } - @JsonIgnore - public abstract String getJSONData(); - - public abstract void setResponse(String response); + public static ModalFormResponse of(int clickedButtonId, String clickedButtonText) { + return new ModalFormResponse(false, false, clickedButtonId, clickedButtonText); + } + public boolean getResult() { + return clickedButtonId == 0; + } } diff --git a/common/src/main/java/org/geysermc/common/window/component/StepSliderComponent.java b/common/src/main/java/org/geysermc/common/form/response/SimpleFormResponse.java similarity index 56% rename from common/src/main/java/org/geysermc/common/window/component/StepSliderComponent.java rename to common/src/main/java/org/geysermc/common/form/response/SimpleFormResponse.java index 8f128d1a4..6ee819f37 100644 --- a/common/src/main/java/org/geysermc/common/window/component/StepSliderComponent.java +++ b/common/src/main/java/org/geysermc/common/form/response/SimpleFormResponse.java @@ -23,47 +23,33 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.common.window.component; +package org.geysermc.common.form.response; +import lombok.AccessLevel; import lombok.Getter; -import lombok.Setter; +import lombok.RequiredArgsConstructor; +import org.geysermc.common.form.component.ButtonComponent; -import java.util.ArrayList; -import java.util.List; +@Getter +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public final class SimpleFormResponse implements FormResponse { + private static final SimpleFormResponse CLOSED = new SimpleFormResponse(true, false, -1, null); + private static final SimpleFormResponse INVALID = new SimpleFormResponse(false, true, -1, null); + private final boolean closed; + private final boolean invalid; -public class StepSliderComponent extends FormComponent { + private final int clickedButtonId; + private final ButtonComponent clickedButton; - @Getter - @Setter - private String text; - - @Getter - private List steps; - - @Getter - @Setter - private int defaultStepIndex; - - public StepSliderComponent(String text) { - this(text, new ArrayList()); + public static SimpleFormResponse closed() { + return CLOSED; } - public StepSliderComponent(String text, List steps) { - this(text, steps, 0); + public static SimpleFormResponse invalid() { + return INVALID; } - public StepSliderComponent(String text, List steps, int defaultStepIndex) { - super("step_slider"); - - this.text = text; - this.steps = steps; - this.defaultStepIndex = defaultStepIndex; - } - - public void addStep(String step, boolean isDefault) { - steps.add(step); - - if (isDefault) - defaultStepIndex = steps.size() - 1; + public static SimpleFormResponse of(int clickedButtonId, ButtonComponent clickedButton) { + return new SimpleFormResponse(false, false, clickedButtonId, clickedButton); } } diff --git a/common/src/main/java/org/geysermc/common/form/util/FormAdaptor.java b/common/src/main/java/org/geysermc/common/form/util/FormAdaptor.java new file mode 100644 index 000000000..8c30e7e8f --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/util/FormAdaptor.java @@ -0,0 +1,126 @@ +/* + * 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.common.form.util; + +import com.google.gson.*; +import com.google.gson.reflect.TypeToken; +import org.geysermc.common.form.CustomForm; +import org.geysermc.common.form.Form; +import org.geysermc.common.form.ModalForm; +import org.geysermc.common.form.SimpleForm; +import org.geysermc.common.form.component.ButtonComponent; +import org.geysermc.common.form.component.Component; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; + +public class FormAdaptor implements JsonDeserializer
, JsonSerializer { + private static final Type LIST_BUTTON_TYPE = + new TypeToken>() {}.getType(); + + @Override + public Form deserialize(JsonElement jsonElement, Type typeOfT, + JsonDeserializationContext context) + throws JsonParseException { + + if (!jsonElement.isJsonObject()) { + throw new JsonParseException("Form has to be a JsonObject"); + } + JsonObject json = jsonElement.getAsJsonObject(); + + if (typeOfT == SimpleForm.class) { + String title = json.get("title").getAsString(); + String content = json.get("content").getAsString(); + List buttons = context + .deserialize(json.get("buttons"), LIST_BUTTON_TYPE); + return SimpleForm.of(title, content, buttons); + } + + if (typeOfT == ModalForm.class) { + String title = json.get("title").getAsString(); + String content = json.get("content").getAsString(); + String button1 = json.get("button1").getAsString(); + String button2 = json.get("button2").getAsString(); + return ModalForm.of(title, content, button1, button2); + } + + if (typeOfT == CustomForm.class) { + String title = json.get("title").getAsString(); + FormImage icon = context.deserialize(json.get("icon"), FormImage.class); + List content = new ArrayList<>(); + + JsonArray contentArray = json.getAsJsonArray("content"); + for (JsonElement contentElement : contentArray) { + String typeName = contentElement.getAsJsonObject().get("type").getAsString(); + + Component.Type type = Component.Type.getByName(typeName); + if (type == null) { + throw new JsonParseException("Failed to find Component type " + typeName); + } + + content.add(context.deserialize(contentElement, type.getComponentClass())); + } + return CustomForm.of(title, icon, content); + } + return null; + } + + @Override + public JsonElement serialize(Form src, Type typeOfSrc, JsonSerializationContext context) { + JsonObject result = new JsonObject(); + result.add("type", context.serialize(src.getType())); + + if (typeOfSrc == SimpleForm.class) { + SimpleForm form = (SimpleForm) src; + + result.addProperty("title", form.getTitle()); + result.addProperty("content", form.getContent()); + result.add("buttons", context.serialize(form.getButtons(), LIST_BUTTON_TYPE)); + return result; + } + + if (typeOfSrc == ModalForm.class) { + ModalForm form = (ModalForm) src; + + result.addProperty("title", form.getTitle()); + result.addProperty("content", form.getContent()); + result.addProperty("button1", form.getButton1()); + result.addProperty("button2", form.getButton2()); + return result; + } + + if (typeOfSrc == CustomForm.class) { + CustomForm form = (CustomForm) src; + + result.addProperty("title", form.getTitle()); + result.add("icon", context.serialize(form.getIcon())); + result.add("content", context.serialize(form.getContent())); + return result; + } + return null; + } +} diff --git a/common/src/main/java/org/geysermc/common/window/button/FormImage.java b/common/src/main/java/org/geysermc/common/form/util/FormImage.java similarity index 72% rename from common/src/main/java/org/geysermc/common/window/button/FormImage.java rename to common/src/main/java/org/geysermc/common/form/util/FormImage.java index 72579f7ac..876e662d0 100644 --- a/common/src/main/java/org/geysermc/common/window/button/FormImage.java +++ b/common/src/main/java/org/geysermc/common/form/util/FormImage.java @@ -23,36 +23,32 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.common.window.button; +package org.geysermc.common.form.util; +import lombok.AccessLevel; import lombok.Getter; -import lombok.Setter; +import lombok.RequiredArgsConstructor; +@Getter +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) public class FormImage { + private final String type; + private final String data; - @Getter - @Setter - private String type; - - @Getter - @Setter - private String data; - - public FormImage(FormImageType type, String data) { - this.type = type.getName(); - this.data = data; + public static FormImage of(String type, String data) { + return new FormImage(type, data); } - public enum FormImageType { + public static FormImage of(Type type, String data) { + return of(type.getName(), data); + } + + @RequiredArgsConstructor + public enum Type { PATH("path"), URL("url"); - @Getter - private String name; - - FormImageType(String name) { - this.name = name; - } + @Getter private final String name; @Override public String toString() { diff --git a/common/src/main/java/org/geysermc/common/window/CustomFormBuilder.java b/common/src/main/java/org/geysermc/common/window/CustomFormBuilder.java deleted file mode 100644 index 004b00a96..000000000 --- a/common/src/main/java/org/geysermc/common/window/CustomFormBuilder.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.window; - -import lombok.Getter; -import org.geysermc.common.window.CustomFormWindow; -import org.geysermc.common.window.button.FormImage; -import org.geysermc.common.window.component.FormComponent; -import org.geysermc.common.window.response.CustomFormResponse; - -public class CustomFormBuilder { - - @Getter - private CustomFormWindow form; - - public CustomFormBuilder(String title) { - form = new CustomFormWindow(title); - } - - public CustomFormBuilder setTitle(String title) { - form.setTitle(title); - return this; - } - - public CustomFormBuilder setIcon(FormImage icon) { - form.setIcon(icon); - return this; - } - - public CustomFormBuilder setResponse(String data) { - form.setResponse(data); - return this; - } - - public CustomFormBuilder setResponse(CustomFormResponse response) { - form.setResponse(response); - return this; - } - - public CustomFormBuilder addComponent(FormComponent component) { - form.addComponent(component); - return this; - } - - public CustomFormWindow build() { - return form; - } -} diff --git a/common/src/main/java/org/geysermc/common/window/CustomFormWindow.java b/common/src/main/java/org/geysermc/common/window/CustomFormWindow.java deleted file mode 100644 index efc71ae8d..000000000 --- a/common/src/main/java/org/geysermc/common/window/CustomFormWindow.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.window; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.Getter; -import lombok.Setter; -import org.geysermc.common.window.button.FormImage; -import org.geysermc.common.window.component.*; -import org.geysermc.common.window.response.CustomFormResponse; -import org.geysermc.common.window.response.FormResponseData; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class CustomFormWindow extends FormWindow { - - @Getter - @Setter - private String title; - - @Getter - @Setter - private FormImage icon; - - @Getter - private List content; - - public CustomFormWindow(String title) { - this(title, new ArrayList<>()); - } - - public CustomFormWindow(String title, List content) { - this(title, content, (FormImage) null); - } - - public CustomFormWindow(String title, List content, String icon) { - this(title, content, new FormImage(FormImage.FormImageType.URL, icon)); - } - - public CustomFormWindow(String title, List content, FormImage icon) { - super("custom_form"); - - this.title = title; - this.content = content; - this.icon = icon; - } - - public void addComponent(FormComponent component) { - content.add(component); - } - - public String getJSONData() { - String toModify = ""; - try { - toModify = new ObjectMapper().writeValueAsString(this); - } catch (JsonProcessingException e) { } - - //We need to replace this due to Java not supporting declaring class field 'default' - return toModify.replace("defaultOptionIndex", "default") - .replace("defaultText", "default") - .replace("defaultValue", "default") - .replace("defaultStepIndex", "default"); - } - - public void setResponse(String data) { - if (data == null || data.equalsIgnoreCase("null") || data.isEmpty()) { - closed = true; - return; - } - - int i = 0; - Map dropdownResponses = new HashMap(); - Map inputResponses = new HashMap(); - Map sliderResponses = new HashMap(); - Map stepSliderResponses = new HashMap(); - Map toggleResponses = new HashMap(); - Map responses = new HashMap(); - Map labelResponses = new HashMap(); - - List componentResponses = new ArrayList<>(); - try { - componentResponses = new ObjectMapper().readValue(data, new TypeReference>(){}); - } catch (IOException e) { } - - for (String response : componentResponses) { - if (i >= content.size()) { - break; - } - - FormComponent component = content.get(i); - if (component == null) - return; - - if (component instanceof LabelComponent) { - LabelComponent labelComponent = (LabelComponent) component; - labelResponses.put(i, labelComponent.getText()); - } - - if (component instanceof DropdownComponent) { - DropdownComponent dropdownComponent = (DropdownComponent) component; - String option = dropdownComponent.getOptions().get(Integer.parseInt(response)); - - dropdownResponses.put(i, new FormResponseData(Integer.parseInt(response), option)); - responses.put(i, option); - } - - if (component instanceof InputComponent) { - inputResponses.put(i, response); - responses.put(i, response); - } - - if (component instanceof SliderComponent) { - float value = Float.parseFloat(response); - sliderResponses.put(i, value); - responses.put(i, value); - } - - if (component instanceof StepSliderComponent) { - StepSliderComponent stepSliderComponent = (StepSliderComponent) component; - String step = stepSliderComponent.getSteps().get(Integer.parseInt(response)); - stepSliderResponses.put(i, new FormResponseData(Integer.parseInt(response), step)); - responses.put(i, step); - } - - if (component instanceof ToggleComponent) { - boolean answer = Boolean.parseBoolean(response); - toggleResponses.put(i, answer); - responses.put(i, answer); - } - i++; - } - - this.response = new CustomFormResponse(responses, dropdownResponses, inputResponses, - sliderResponses, stepSliderResponses, toggleResponses, labelResponses); - } -} diff --git a/common/src/main/java/org/geysermc/common/window/ModalFormWindow.java b/common/src/main/java/org/geysermc/common/window/ModalFormWindow.java deleted file mode 100644 index bfeafa1b0..000000000 --- a/common/src/main/java/org/geysermc/common/window/ModalFormWindow.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.window; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.Getter; -import lombok.Setter; -import org.geysermc.common.window.response.ModalFormResponse; - -public class ModalFormWindow extends FormWindow { - - @Getter - @Setter - private String title; - - @Getter - @Setter - private String content; - - @Getter - @Setter - private String button1; - - @Getter - @Setter - private String button2; - - public ModalFormWindow(String title, String content, String button1, String button2) { - super("modal"); - - this.title = title; - this.content = content; - this.button1 = button1; - this.button2 = button2; - } - - @Override - public String getJSONData() { - try { - return new ObjectMapper().writeValueAsString(this); - } catch (JsonProcessingException e) { - return ""; - } - } - - public void setResponse(String data) { - if (data == null || data.equalsIgnoreCase("null")) { - closed = true; - return; - } - - if (Boolean.parseBoolean(data)) { - response = new ModalFormResponse(0, button1); - } else { - response = new ModalFormResponse(1, button2); - } - } -} diff --git a/common/src/main/java/org/geysermc/common/window/SimpleFormWindow.java b/common/src/main/java/org/geysermc/common/window/SimpleFormWindow.java deleted file mode 100644 index 7c1acc26f..000000000 --- a/common/src/main/java/org/geysermc/common/window/SimpleFormWindow.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.window; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.Getter; -import lombok.Setter; -import org.geysermc.common.window.button.FormButton; -import org.geysermc.common.window.response.SimpleFormResponse; - -import java.util.ArrayList; -import java.util.List; - - -public class SimpleFormWindow extends FormWindow { - - @Getter - @Setter - private String title; - - @Getter - @Setter - private String content; - - @Getter - @Setter - private List buttons; - - public SimpleFormWindow(String title, String content) { - this(title, content, new ArrayList()); - } - - public SimpleFormWindow(String title, String content, List buttons) { - super("form"); - - this.title = title; - this.content = content; - this.buttons = buttons; - } - - @Override - public String getJSONData() { - try { - return new ObjectMapper().writeValueAsString(this); - } catch (JsonProcessingException e) { - return ""; - } - } - - public void setResponse(String data) { - if (data == null || data.equalsIgnoreCase("null")) { - closed = true; - return; - } - - int buttonID; - try { - buttonID = Integer.parseInt(data); - } catch (Exception ex) { - return; - } - - if (buttonID >= buttons.size()) { - response = new SimpleFormResponse(buttonID, null); - return; - } - - response = new SimpleFormResponse(buttonID, buttons.get(buttonID)); - } -} diff --git a/common/src/main/java/org/geysermc/common/window/component/DropdownComponent.java b/common/src/main/java/org/geysermc/common/window/component/DropdownComponent.java deleted file mode 100644 index 4dac6b043..000000000 --- a/common/src/main/java/org/geysermc/common/window/component/DropdownComponent.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.window.component; - -import lombok.Getter; -import lombok.Setter; - -import java.util.List; - -public class DropdownComponent extends FormComponent { - - @Getter - @Setter - private String text; - - @Getter - @Setter - private List options; - - @Getter - @Setter - private int defaultOptionIndex; - - public DropdownComponent() { - super("dropdown"); - } - - public void addOption(String option, boolean isDefault) { - options.add(option); - if (isDefault) - defaultOptionIndex = options.size() - 1; - } -} diff --git a/common/src/main/java/org/geysermc/common/window/component/FormComponent.java b/common/src/main/java/org/geysermc/common/window/component/FormComponent.java deleted file mode 100644 index 5a56ae0cc..000000000 --- a/common/src/main/java/org/geysermc/common/window/component/FormComponent.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.window.component; - -import lombok.Getter; - -public abstract class FormComponent { - - @Getter - private final String type; - - public FormComponent(String type) { - this.type = type; - } -} diff --git a/common/src/main/java/org/geysermc/common/window/component/SliderComponent.java b/common/src/main/java/org/geysermc/common/window/component/SliderComponent.java deleted file mode 100644 index a7a78362e..000000000 --- a/common/src/main/java/org/geysermc/common/window/component/SliderComponent.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.window.component; - -import lombok.Getter; -import lombok.Setter; - -public class SliderComponent extends FormComponent { - - @Getter - @Setter - private String text; - - @Getter - @Setter - private float min; - - @Getter - @Setter - private float max; - - @Getter - @Setter - private int step; - - @Getter - @Setter - private float defaultValue; - - public SliderComponent(String text, float min, float max, int step, float defaultValue) { - super("slider"); - - this.text = text; - this.min = Math.max(min, 0f); - this.max = max > this.min ? max : this.min; - if (step != -1f && step > 0) - this.step = step; - - if (defaultValue != -1f) - this.defaultValue = defaultValue; - } -} diff --git a/common/src/main/java/org/geysermc/common/window/response/CustomFormResponse.java b/common/src/main/java/org/geysermc/common/window/response/CustomFormResponse.java deleted file mode 100644 index 6cdd70978..000000000 --- a/common/src/main/java/org/geysermc/common/window/response/CustomFormResponse.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.window.response; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import org.geysermc.common.window.response.FormResponse; -import org.geysermc.common.window.response.FormResponseData; - -import java.util.Map; - -@Getter -@AllArgsConstructor -public class CustomFormResponse implements FormResponse { - - private Map responses; - private Map dropdownResponses; - private Map inputResponses; - private Map sliderResponses; - private Map stepSliderResponses; - private Map toggleResponses; - private Map labelResponses; -} diff --git a/common/src/main/java/org/geysermc/common/window/response/FormResponseData.java b/common/src/main/java/org/geysermc/common/window/response/FormResponseData.java deleted file mode 100644 index fd40be0fb..000000000 --- a/common/src/main/java/org/geysermc/common/window/response/FormResponseData.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.window.response; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -@AllArgsConstructor -@Getter -public class FormResponseData { - - private int elementID; - private String elementContent; -} diff --git a/common/src/main/java/org/geysermc/common/window/response/ModalFormResponse.java b/common/src/main/java/org/geysermc/common/window/response/ModalFormResponse.java deleted file mode 100644 index e1a14039d..000000000 --- a/common/src/main/java/org/geysermc/common/window/response/ModalFormResponse.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.window.response; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import org.geysermc.common.window.response.FormResponse; - -@Getter -@AllArgsConstructor -public class ModalFormResponse implements FormResponse { - - private int clickedButtonId; - private String clickedButtonText; -} diff --git a/common/src/main/java/org/geysermc/common/window/response/SimpleFormResponse.java b/common/src/main/java/org/geysermc/common/window/response/SimpleFormResponse.java deleted file mode 100644 index e80d58e78..000000000 --- a/common/src/main/java/org/geysermc/common/window/response/SimpleFormResponse.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.window.response; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import org.geysermc.common.window.button.FormButton; -import org.geysermc.common.window.response.FormResponse; - -@Getter -@AllArgsConstructor -public class SimpleFormResponse implements FormResponse { - - private int clickedButtonId; - private FormButton clickedButton; -} 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 7e97d4298..9f6a88e18 100644 --- a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java @@ -39,7 +39,6 @@ import org.geysermc.connector.utils.LoginEncryptionUtils; import org.geysermc.connector.utils.MathUtils; import org.geysermc.connector.utils.ResourcePack; import org.geysermc.connector.utils.ResourcePackManifest; -import org.geysermc.connector.utils.SettingsUtils; import java.io.FileInputStream; import java.io.InputStream; @@ -139,11 +138,8 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { @Override public boolean handle(ModalFormResponsePacket packet) { - if (packet.getFormId() == SettingsUtils.SETTINGS_FORM_ID) { - return SettingsUtils.handleSettingsForm(session, packet.getFormData()); - } - - return LoginEncryptionUtils.authenticateFromForm(session, connector, packet.getFormId(), packet.getFormData()); + session.getFormCache().handleResponse(packet); + return true; } private boolean couldLoginUserByName(String bedrockUsername) { @@ -170,7 +166,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { 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())) { - LoginEncryptionUtils.showLoginWindow(session); + LoginEncryptionUtils.buildAndShowLoginWindow(session); } // else we were able to log the user in } 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 42c2f2437..ec8c7f0ee 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 @@ -55,8 +55,7 @@ import it.unimi.dsi.fastutil.objects.Object2LongMap; import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap; import lombok.Getter; import lombok.Setter; -import org.geysermc.common.window.CustomFormWindow; -import org.geysermc.common.window.FormWindow; +import org.geysermc.common.form.Form; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.CommandSender; import org.geysermc.connector.common.AuthType; @@ -101,7 +100,7 @@ public class GeyserSession implements CommandSender { private EntityCache entityCache; private InventoryCache inventoryCache; private WorldCache worldCache; - private WindowCache windowCache; + private FormCache formCache; @Setter private TeleportCache teleportCache; @@ -192,9 +191,6 @@ public class GeyserSession implements CommandSender { private boolean reducedDebugInfo = false; - @Setter - private CustomFormWindow settingsForm; - /** * The op permission level set by the server */ @@ -239,7 +235,7 @@ public class GeyserSession implements CommandSender { /** * Stores the last text inputted into a sign. - * + *

* Bedrock sends packets every time you update the sign, Java only wants the final packet. * Until we determine that the user has finished editing, we save the sign's current status. */ @@ -256,7 +252,7 @@ public class GeyserSession implements CommandSender { this.entityCache = new EntityCache(this); this.inventoryCache = new InventoryCache(this); this.worldCache = new WorldCache(this); - this.windowCache = new WindowCache(this); + this.formCache = new FormCache(this); this.playerEntity = new PlayerEntity(new GameProfile(UUID.randomUUID(), "unknown"), 1, 1, Vector3f.ZERO, Vector3f.ZERO, Vector3f.ZERO); this.inventory = new PlayerInventory(); @@ -497,7 +493,7 @@ public class GeyserSession implements CommandSender { this.entityCache = null; this.worldCache = null; this.inventoryCache = null; - this.windowCache = null; + this.formCache = null; closed = true; } @@ -533,10 +529,6 @@ public class GeyserSession implements CommandSender { return false; } - public void sendForm(FormWindow window, int id) { - windowCache.showWindow(window, id); - } - public void setRenderDistance(int renderDistance) { renderDistance = GenericMath.ceil(++renderDistance * MathUtils.SQRT_OF_TWO); //square to circle if (renderDistance > 32) renderDistance = 32; // <3 u ViaVersion but I don't like crashing clients x) @@ -551,8 +543,12 @@ public class GeyserSession implements CommandSender { return this.upstream.getAddress(); } - public void sendForm(FormWindow window) { - windowCache.showWindow(window); + public void sendForm(Form form) { + formCache.showForm(form); + } + + public void sendForm(Form.Builder formBuilder) { + formCache.showForm(formBuilder.build()); } private void startGame() { @@ -678,7 +674,7 @@ public class GeyserSession implements CommandSender { * Send a gamerule value to the client * * @param gameRule The gamerule to send - * @param value The value of the gamerule + * @param value The value of the gamerule */ public void sendGameRule(String gameRule, Object value) { GameRulesChangedPacket gameRulesChangedPacket = new GameRulesChangedPacket(); diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/WindowCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java similarity index 56% rename from connector/src/main/java/org/geysermc/connector/network/session/cache/WindowCache.java rename to connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java index 15b9a7705..20ade2869 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/WindowCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java @@ -26,56 +26,54 @@ package org.geysermc.connector.network.session.cache; import com.nukkitx.protocol.bedrock.packet.ModalFormRequestPacket; - +import com.nukkitx.protocol.bedrock.packet.ModalFormResponsePacket; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; - -import lombok.Getter; - -import org.geysermc.common.window.FormWindow; +import lombok.RequiredArgsConstructor; +import org.geysermc.common.form.Form; import org.geysermc.connector.network.session.GeyserSession; -public class WindowCache { +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; - private GeyserSession session; +@RequiredArgsConstructor +public class FormCache { + private final AtomicInteger formId = new AtomicInteger(0); + private final Int2ObjectMap forms = new Int2ObjectOpenHashMap<>(); + private final GeyserSession session; - @Getter - private Int2ObjectMap windows = new Int2ObjectOpenHashMap<>(); - - public WindowCache(GeyserSession session) { - this.session = session; + public int addForm(Form form) { + int windowId = formId.getAndIncrement(); + forms.put(windowId, form); + return windowId; } - public void addWindow(FormWindow window) { - windows.put(windows.size() + 1, window); + public int showForm(Form form) { + int windowId = addForm(form); + + ModalFormRequestPacket formRequestPacket = new ModalFormRequestPacket(); + formRequestPacket.setFormId(windowId); + formRequestPacket.setFormData(form.getJsonData()); + + session.sendUpstreamPacket(formRequestPacket); + return windowId; } - public void addWindow(FormWindow window, int id) { - windows.put(id, window); - } - - public void showWindow(FormWindow window) { - showWindow(window, windows.size() + 1); - } - - public void showWindow(int id) { - if (!windows.containsKey(id)) + public void handleResponse(ModalFormResponsePacket response) { + Form form = forms.get(response.getFormId()); + if (form == null) { return; + } - ModalFormRequestPacket formRequestPacket = new ModalFormRequestPacket(); - formRequestPacket.setFormId(id); - formRequestPacket.setFormData(windows.get(id).getJSONData()); + Consumer responseConsumer = form.getResponseHandler(); + if (responseConsumer != null) { + responseConsumer.accept(response.getFormData().trim()); + } - session.sendUpstreamPacket(formRequestPacket); + removeWindow(response.getFormId()); } - public void showWindow(FormWindow window, int id) { - ModalFormRequestPacket formRequestPacket = new ModalFormRequestPacket(); - formRequestPacket.setFormId(id); - formRequestPacket.setFormData(window.getJSONData()); - - session.sendUpstreamPacket(formRequestPacket); - - addWindow(window, id); + public boolean removeWindow(int id) { + return forms.remove(id) != null; } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockServerSettingsRequestTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockServerSettingsRequestTranslator.java index a8591cd7f..01bf07b9c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockServerSettingsRequestTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockServerSettingsRequestTranslator.java @@ -27,6 +27,7 @@ package org.geysermc.connector.network.translators.bedrock; import com.nukkitx.protocol.bedrock.packet.ServerSettingsRequestPacket; import com.nukkitx.protocol.bedrock.packet.ServerSettingsResponsePacket; +import org.geysermc.common.form.CustomForm; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; @@ -37,11 +38,12 @@ public class BedrockServerSettingsRequestTranslator extends PacketTranslator { - - private static byte[] brandData; + private static final byte[] brandData; static { byte[] data = GeyserConnector.NAME.getBytes(StandardCharsets.UTF_8); @@ -48,21 +54,11 @@ public class JavaPluginMessageTranslator extends PacketTranslator>>= 7; if (value != 0) { temp |= 0b10000000; @@ -85,4 +81,48 @@ public class JavaPluginMessageTranslator extends PacketTranslator { + byte[] raw = response.getBytes(StandardCharsets.UTF_8); + byte[] finalData = new byte[raw.length + 2]; + + finalData[0] = data[1]; + finalData[1] = data[2]; + System.arraycopy(raw, 0, finalData, 2, raw.length); + + session.sendDownstreamPacket(new ClientPluginMessagePacket(channel, finalData)); + }); + session.sendForm(form); + } + } } 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 62d70f612..8109ef219 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java @@ -34,20 +34,16 @@ import com.nukkitx.network.util.Preconditions; import com.nukkitx.protocol.bedrock.packet.LoginPacket; import com.nukkitx.protocol.bedrock.packet.ServerToClientHandshakePacket; import com.nukkitx.protocol.bedrock.util.EncryptionUtils; -import org.geysermc.common.window.CustomFormBuilder; -import org.geysermc.common.window.CustomFormWindow; -import org.geysermc.common.window.FormWindow; -import org.geysermc.common.window.SimpleFormWindow; -import org.geysermc.common.window.button.FormButton; -import org.geysermc.common.window.component.InputComponent; -import org.geysermc.common.window.component.LabelComponent; -import org.geysermc.common.window.response.CustomFormResponse; -import org.geysermc.common.window.response.SimpleFormResponse; +import org.geysermc.common.form.CustomForm; +import org.geysermc.common.form.ModalForm; +import org.geysermc.common.form.SimpleForm; +import org.geysermc.common.form.response.CustomFormResponse; +import org.geysermc.common.form.response.ModalFormResponse; +import org.geysermc.common.form.response.SimpleFormResponse; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.auth.AuthData; import org.geysermc.connector.network.session.auth.BedrockClientData; -import org.geysermc.connector.network.session.cache.WindowCache; import javax.crypto.SecretKey; import java.io.IOException; @@ -72,7 +68,7 @@ public class LoginEncryptionUtils { } if (lastKey != null) { - if (!EncryptionUtils.verifyJwt(jwt, lastKey)) return false; + if (!EncryptionUtils.verifyJwt(jwt, lastKey)) return false; } JsonNode payloadNode = JSON_MAPPER.readTree(jwt.getPayload().toString()); @@ -159,69 +155,49 @@ public class LoginEncryptionUtils { session.sendUpstreamPacketImmediately(packet); } - private static int AUTH_FORM_ID = 1336; - private static int AUTH_DETAILS_FORM_ID = 1337; - - public static void showLoginWindow(GeyserSession session) { + public static void buildAndShowLoginWindow(GeyserSession session) { 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); - } + session.sendForm( + SimpleForm.builder() + .translator(LanguageUtils::getPlayerLocaleString, userLanguage) + .title("geyser.auth.login.form.notice.title") + .content("geyser.auth.login.form.notice.desc") + .button("geyser.auth.login.form.notice.btn_login") // id = 0 + .button("geyser.auth.login.form.notice.btn_disconnect") + .responseHandler((form, responseData) -> { + SimpleFormResponse response = form.parseResponse(responseData.trim()); + if (!response.isCorrect()) { + buildAndShowLoginWindow(session); + return; + } - public static void showLoginDetailsWindow(GeyserSession session) { - 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(); + if (response.getClickedButtonId() == 0) { + buildAndShowLoginDetailsWindow(session); + return; + } - session.sendForm(window, AUTH_DETAILS_FORM_ID); - } - - public static boolean authenticateFromForm(GeyserSession session, GeyserConnector connector, int formId, String formData) { - WindowCache windowCache = session.getWindowCache(); - if (!windowCache.getWindows().containsKey(formId)) - return false; - - if(formId == AUTH_FORM_ID || formId == AUTH_DETAILS_FORM_ID) { - FormWindow window = windowCache.getWindows().remove(formId); - window.setResponse(formData.trim()); - - if (!session.isLoggedIn()) { - if (formId == AUTH_DETAILS_FORM_ID && window instanceof CustomFormWindow) { - CustomFormWindow customFormWindow = (CustomFormWindow) window; - - CustomFormResponse response = (CustomFormResponse) customFormWindow.getResponse(); - if (response != null) { - String email = response.getInputResponses().get(1); - String password = response.getInputResponses().get(2); - - session.authenticate(email, password); - } else { - showLoginDetailsWindow(session); - } - - // Clear windows so authentication data isn't accidentally cached - windowCache.getWindows().clear(); - } else if (formId == AUTH_FORM_ID && window instanceof SimpleFormWindow) { - SimpleFormResponse response = (SimpleFormResponse) window.getResponse(); - if (response != null) { - if (response.getClickedButtonId() == 0) { - showLoginDetailsWindow(session); - } else if(response.getClickedButtonId() == 1) { session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.getClientData().getLanguageCode())); - } - } else { - showLoginWindow(session); - } - } - } - } - return true; + })); } + public static void buildAndShowLoginDetailsWindow(GeyserSession session) { + String userLanguage = session.getClientData().getLanguageCode(); + session.sendForm( + CustomForm.builder() + .translator(LanguageUtils::getPlayerLocaleString, userLanguage) + .title("geyser.auth.login.form.details.title") + .label("geyser.auth.login.form.details.desc") + .input("geyser.auth.login.form.details.email", "account@geysermc.org", "") + .input("geyser.auth.login.form.details.pass", "123456", "") + .responseHandler((form, responseData) -> { + CustomFormResponse response = form.parseResponse(responseData.trim()); + if (!response.isCorrect()) { + buildAndShowLoginDetailsWindow(session); + return; + } + + session.authenticate(response.next(), response.next()); + })); + } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java b/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java index 89e9fe67b..a2f6aeba4 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java @@ -27,62 +27,53 @@ package org.geysermc.connector.utils; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import com.github.steveice10.mc.protocol.data.game.setting.Difficulty; -import org.geysermc.common.window.CustomFormBuilder; -import org.geysermc.common.window.CustomFormWindow; -import org.geysermc.common.window.button.FormImage; -import org.geysermc.common.window.component.DropdownComponent; -import org.geysermc.common.window.component.InputComponent; -import org.geysermc.common.window.component.LabelComponent; -import org.geysermc.common.window.component.ToggleComponent; -import org.geysermc.common.window.response.CustomFormResponse; +import org.geysermc.common.form.CustomForm; +import org.geysermc.common.form.component.DropdownComponent; +import org.geysermc.common.form.response.CustomFormResponse; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; - -import java.util.ArrayList; +import org.geysermc.connector.network.translators.world.WorldManager; public class SettingsUtils { - // Used in UpstreamPacketHandler.java - public static final int SETTINGS_FORM_ID = 1338; - /** * Build a settings form for the given session and store it for later * * @param session The session to build the form for */ - public static void buildForm(GeyserSession session) { + public static CustomForm buildForm(GeyserSession session) { // Cache the language for cleaner access String language = session.getClientData().getLanguageCode(); - CustomFormBuilder builder = new CustomFormBuilder(LanguageUtils.getPlayerLocaleString("geyser.settings.title.main", language)); - builder.setIcon(new FormImage(FormImage.FormImageType.PATH, "textures/ui/settings_glyph_color_2x.png")); - - builder.addComponent(new LabelComponent(LanguageUtils.getPlayerLocaleString("geyser.settings.title.client", language))); - builder.addComponent(new ToggleComponent(LanguageUtils.getPlayerLocaleString("geyser.settings.option.coordinates", language, session.getWorldCache().isShowCoordinates()))); + CustomForm.Builder builder = CustomForm.builder() + .translator(LanguageUtils::getPlayerLocaleString, language) + .title("geyser.settings.title.main") + .iconPath("textures/ui/settings_glyph_color_2x.png") + .label("geyser.settings.title.client") + .toggle("geyser.settings.option.coordinates"); if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.server")) { - builder.addComponent(new LabelComponent(LanguageUtils.getPlayerLocaleString("geyser.settings.title.server", language))); + builder.label("geyser.settings.title.server"); - DropdownComponent gamemodeDropdown = new DropdownComponent(); - gamemodeDropdown.setText("%createWorldScreen.gameMode.personal"); - gamemodeDropdown.setOptions(new ArrayList<>()); + DropdownComponent.Builder gamemodeDropdown = DropdownComponent.builder("%createWorldScreen.gameMode.personal"); for (GameMode gamemode : GameMode.values()) { - gamemodeDropdown.addOption(LocaleUtils.getLocaleString("selectWorld.gameMode." + gamemode.name().toLowerCase(), language), session.getGameMode() == gamemode); + gamemodeDropdown.option("selectWorld.gameMode." + gamemode.name().toLowerCase(), session.getGameMode() == gamemode); } - builder.addComponent(gamemodeDropdown); + builder.dropdown(gamemodeDropdown); - DropdownComponent difficultyDropdown = new DropdownComponent(); - difficultyDropdown.setText("%options.difficulty"); - difficultyDropdown.setOptions(new ArrayList<>()); + DropdownComponent.Builder difficultyDropdown = DropdownComponent.builder("%options.difficulty"); for (Difficulty difficulty : Difficulty.values()) { - difficultyDropdown.addOption("%options.difficulty." + difficulty.name().toLowerCase(), session.getWorldCache().getDifficulty() == difficulty); + difficultyDropdown.option("%options.difficulty." + difficulty.name().toLowerCase(), session.getWorldCache().getDifficulty() == difficulty); } - builder.addComponent(difficultyDropdown); + builder.dropdown(difficultyDropdown); } if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.gamerules")) { - builder.addComponent(new LabelComponent(LanguageUtils.getPlayerLocaleString("geyser.settings.title.game_rules", language))); + builder.label("geyser.settings.title.game_rules") + .translator(LocaleUtils::getLocaleString); // we need translate gamerules next + + WorldManager worldManager = GeyserConnector.getInstance().getWorldManager(); for (GameRule gamerule : GameRule.values()) { if (gamerule.equals(GameRule.UNKNOWN)) { continue; @@ -90,74 +81,54 @@ public class SettingsUtils { // Add the relevant form item based on the gamerule type if (Boolean.class.equals(gamerule.getType())) { - builder.addComponent(new ToggleComponent(LocaleUtils.getLocaleString("gamerule." + gamerule.getJavaID(), language), GeyserConnector.getInstance().getWorldManager().getGameRuleBool(session, gamerule))); + builder.toggle("gamerule." + gamerule.getJavaID(), worldManager.getGameRuleBool(session, gamerule)); } else if (Integer.class.equals(gamerule.getType())) { - builder.addComponent(new InputComponent(LocaleUtils.getLocaleString("gamerule." + gamerule.getJavaID(), language), "", String.valueOf(GeyserConnector.getInstance().getWorldManager().getGameRuleInt(session, gamerule)))); + builder.input("gamerule." + gamerule.getJavaID(), "", String.valueOf(worldManager.getGameRuleInt(session, gamerule))); } } } - session.setSettingsForm(builder.build()); - } - - /** - * Handle the settings form response - * - * @param session The session that sent the response - * @param response The response string to parse - * @return True if the form was parsed correctly, false if not - */ - public static boolean handleSettingsForm(GeyserSession session, String response) { - CustomFormWindow settingsForm = session.getSettingsForm(); - settingsForm.setResponse(response); - - CustomFormResponse settingsResponse = (CustomFormResponse) settingsForm.getResponse(); - int offset = 0; - - offset++; // Client settings title - - session.getWorldCache().setShowCoordinates(settingsResponse.getToggleResponses().get(offset)); - offset++; - - if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.server")) { - offset++; // Server settings title - - GameMode gameMode = GameMode.values()[settingsResponse.getDropdownResponses().get(offset).getElementID()]; - if (gameMode != null && gameMode != session.getGameMode()) { - session.getConnector().getWorldManager().setPlayerGameMode(session, gameMode); + builder.responseHandler((form, responseData) -> { + CustomFormResponse response = form.parseResponseAs(responseData); + if (response.isClosed() || response.isInvalid()) { + return; } - offset++; - Difficulty difficulty = Difficulty.values()[settingsResponse.getDropdownResponses().get(offset).getElementID()]; - if (difficulty != null && difficulty != session.getWorldCache().getDifficulty()) { - session.getConnector().getWorldManager().setDifficulty(session, difficulty); - } - offset++; - } + session.getWorldCache().setShowCoordinates(response.next()); - if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.gamerules")) { - offset++; // Game rule title - - for (GameRule gamerule : GameRule.values()) { - if (gamerule.equals(GameRule.UNKNOWN)) { - continue; + if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.server")) { + GameMode gameMode = GameMode.values()[(int) response.next()]; + if (gameMode != null && gameMode != session.getGameMode()) { + session.getConnector().getWorldManager().setPlayerGameMode(session, gameMode); } - if (Boolean.class.equals(gamerule.getType())) { - Boolean value = settingsResponse.getToggleResponses().get(offset).booleanValue(); - if (value != session.getConnector().getWorldManager().getGameRuleBool(session, gamerule)) { - session.getConnector().getWorldManager().setGameRule(session, gamerule.getJavaID(), value); + Difficulty difficulty = Difficulty.values()[(int) response.next()]; + if (difficulty != null && difficulty != session.getWorldCache().getDifficulty()) { + session.getConnector().getWorldManager().setDifficulty(session, difficulty); + } + } + + if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.gamerules")) { + for (GameRule gamerule : GameRule.values()) { + if (gamerule.equals(GameRule.UNKNOWN)) { + continue; } - } else if (Integer.class.equals(gamerule.getType())) { - int value = Integer.parseInt(settingsResponse.getInputResponses().get(offset)); - if (value != session.getConnector().getWorldManager().getGameRuleInt(session, gamerule)) { - session.getConnector().getWorldManager().setGameRule(session, gamerule.getJavaID(), value); + + if (Boolean.class.equals(gamerule.getType())) { + boolean value = response.next(); + if (value != session.getConnector().getWorldManager().getGameRuleBool(session, gamerule)) { + session.getConnector().getWorldManager().setGameRule(session, gamerule.getJavaID(), value); + } + } else if (Integer.class.equals(gamerule.getType())) { + int value = Integer.parseInt(response.next()); + if (value != session.getConnector().getWorldManager().getGameRuleInt(session, gamerule)) { + session.getConnector().getWorldManager().setGameRule(session, gamerule.getJavaID(), value); + } } } - offset++; } - } + }); - return true; + return builder.build(); } } From dc8fb4642818012118b61411c83aa32e24283dc7 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Fri, 30 Oct 2020 01:14:34 +0100 Subject: [PATCH 009/107] Manually solve some merge issues --- .../java/org/geysermc/common/form/Form.java | 12 ++- .../java/JavaPluginMessageTranslator.java | 78 +++++++++++++++++++ 2 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/java/JavaPluginMessageTranslator.java diff --git a/common/src/main/java/org/geysermc/common/form/Form.java b/common/src/main/java/org/geysermc/common/form/Form.java index 5b3437572..5924e6896 100644 --- a/common/src/main/java/org/geysermc/common/form/Form.java +++ b/common/src/main/java/org/geysermc/common/form/Form.java @@ -26,12 +26,14 @@ package org.geysermc.common.form; import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import com.google.gson.annotations.SerializedName; import lombok.AccessLevel; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.Setter; import org.geysermc.common.form.response.FormResponse; +import org.geysermc.common.form.util.FormAdaptor; import java.util.function.BiConsumer; import java.util.function.BiFunction; @@ -39,7 +41,11 @@ import java.util.function.Consumer; @Getter public abstract class Form { - protected static final Gson GSON = new Gson(); + protected static final Gson GSON = + new GsonBuilder() + .registerTypeAdapter(ModalForm.class, new FormAdaptor()) + .create(); + private final Type type; @Getter(AccessLevel.NONE) @@ -51,6 +57,10 @@ public abstract class Form { this.type = type; } + public static T fromJson(String json, Class formClass) { + return GSON.fromJson(json, formClass); + } + public String getJsonData() { if (hardcodedJsonData != null) { return hardcodedJsonData; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaPluginMessageTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaPluginMessageTranslator.java new file mode 100644 index 000000000..2a791d5a2 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaPluginMessageTranslator.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.java; + +import com.github.steveice10.mc.protocol.packet.ingame.client.ClientPluginMessagePacket; +import com.github.steveice10.mc.protocol.packet.ingame.server.ServerPluginMessagePacket; +import com.google.common.base.Charsets; +import org.geysermc.common.form.Form; +import org.geysermc.connector.common.AuthType; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; + +import java.nio.charset.StandardCharsets; + +@Translator(packet = ServerPluginMessagePacket.class) +public class JavaPluginMessageTranslator extends PacketTranslator { + @Override + public void translate(ServerPluginMessagePacket packet, GeyserSession session) { + // The only plugin messages to listen for are Floodgate plugin messages + if (session.getConnector().getAuthType() != AuthType.FLOODGATE) { + return; + } + + String channel = packet.getChannel(); + + if (channel.equals("floodgate:form")) { + byte[] data = packet.getData(); + + // receive: first byte is form type, second and third are the id, remaining is the form data + // respond: first and second byte id, remaining is form response data + + Form.Type type = Form.Type.getByOrdinal(data[0]); + if (type == null) { + throw new NullPointerException( + "Got type " + data[0] + " which isn't a valid form type!"); + } + + String dataString = new String(data, 3, data.length - 3, Charsets.UTF_8); + + Form form = Form.fromJson(dataString, type.getTypeClass()); + form.setResponseHandler(response -> { + byte[] raw = response.getBytes(StandardCharsets.UTF_8); + byte[] finalData = new byte[raw.length + 2]; + + finalData[0] = data[1]; + finalData[1] = data[2]; + System.arraycopy(raw, 0, finalData, 2, raw.length); + + session.sendDownstreamPacket(new ClientPluginMessagePacket(channel, finalData)); + }); + session.sendForm(form); + } + } +} From 819ff09ee6f390f112950212913908006b4b37bb Mon Sep 17 00:00:00 2001 From: Tim203 Date: Fri, 30 Oct 2020 01:31:22 +0100 Subject: [PATCH 010/107] Manually resolved merge conflicts This specific commit isn't compilable, since StatisticsUtils hasn't been updated to match the new Form style. --- .../org/geysermc/connector/network/UpstreamPacketHandler.java | 2 +- .../main/java/org/geysermc/connector/utils/StatisticsUtils.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 72574b591..20d2bb9e7 100644 --- a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java @@ -139,7 +139,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { @Override public boolean handle(ModalFormResponsePacket packet) { session.getFormCache().handleResponse(packet); - return true; //todo change the Statistics Form to match the new style + return true; } private boolean couldLoginUserByName(String bedrockUsername) { diff --git a/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java b/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java index 3c42182d7..732217c24 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java @@ -37,7 +37,7 @@ import org.geysermc.connector.network.translators.world.block.BlockTranslator; import java.util.Map; -public class StatisticsUtils { +public class StatisticsUtils { //todo make Geyser compilable // Used in UpstreamPacketHandler.java public static final int STATISTICS_MENU_FORM_ID = 1339; From 7e3a736f20c9810f86c328f034e907399a338fb7 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Wed, 18 Nov 2020 19:38:49 +0100 Subject: [PATCH 011/107] Register Floodgate payload, updated Statistics, smaller jar, fixed bugs Quite a lot of changes, but I was too lazy to split them in different commits (and they'll be squashed later anyway): * Floodgate plugin message channels are now registered (because Spigot requires that, and I guess it's better practice) * Updated the Statistics form to match the new Forms API * The common jar is now much smaller, because Jackson isn't needed anymore in the common module * Fixed some bugs in Forms where empty fields would lead to excluding them in the serialization (making Bedrock complain) And a few other things, like a new boolean in RawSkin saying if the Skin is an Alex or Steve model. --- common/pom.xml | 6 - .../org/geysermc/common/form/ModalForm.java | 6 +- .../org/geysermc/common/form/SimpleForm.java | 2 +- .../common/window/CustomFormWindow.java | 0 .../common/window/SimpleFormWindow.java | 0 .../geysermc/floodgate/util/BedrockData.java | 49 +-- .../org/geysermc/floodgate/util/DeviceOs.java | 3 +- .../geysermc/floodgate/util/InputMode.java | 4 +- .../geysermc/floodgate/util/LinkedPlayer.java | 15 +- .../org/geysermc/floodgate/util/RawSkin.java | 33 +- .../geysermc/floodgate/util/UiProfile.java | 7 +- .../geysermc/connector/GeyserConnector.java | 2 +- .../org/geysermc/connector/dump/DumpInfo.java | 2 +- .../connector/entity/FireworkEntity.java | 2 +- .../network/session/GeyserSession.java | 4 +- .../session/auth/BedrockClientData.java | 69 +++- .../java/JavaJoinGameTranslator.java | 7 +- .../java/JavaStatisticsTranslator.java | 3 +- .../connector/utils/LanguageUtils.java | 13 +- .../connector/utils/LoginEncryptionUtils.java | 2 - .../connector/utils/PluginMessageUtils.java | 36 +- .../connector/utils/SkinProvider.java | 3 +- .../connector/utils/StatisticsUtils.java | 317 +++++++++--------- 23 files changed, 324 insertions(+), 261 deletions(-) delete mode 100644 common/src/main/java/org/geysermc/common/window/CustomFormWindow.java delete mode 100644 common/src/main/java/org/geysermc/common/window/SimpleFormWindow.java diff --git a/common/pom.xml b/common/pom.xml index 30c8afc06..9223331be 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -18,11 +18,5 @@ 2.8.5 compile - - com.fasterxml.jackson.datatype - jackson-datatype-jsr310 - 2.9.8 - compile - \ No newline at end of file diff --git a/common/src/main/java/org/geysermc/common/form/ModalForm.java b/common/src/main/java/org/geysermc/common/form/ModalForm.java index 10309f9c2..ea58a466e 100644 --- a/common/src/main/java/org/geysermc/common/form/ModalForm.java +++ b/common/src/main/java/org/geysermc/common/form/ModalForm.java @@ -69,9 +69,9 @@ public class ModalForm extends Form { } public static final class Builder extends Form.Builder { - private String content; - private String button1; - private String button2; + private String content = ""; + private String button1 = ""; + private String button2 = ""; public Builder content(String content) { this.content = translate(content); diff --git a/common/src/main/java/org/geysermc/common/form/SimpleForm.java b/common/src/main/java/org/geysermc/common/form/SimpleForm.java index 1a1ce616b..275fb8a86 100644 --- a/common/src/main/java/org/geysermc/common/form/SimpleForm.java +++ b/common/src/main/java/org/geysermc/common/form/SimpleForm.java @@ -80,7 +80,7 @@ public final class SimpleForm extends Form { public static final class Builder extends Form.Builder { private final List buttons = new ArrayList<>(); - private String content; + private String content = ""; public Builder content(String content) { this.content = translate(content); diff --git a/common/src/main/java/org/geysermc/common/window/CustomFormWindow.java b/common/src/main/java/org/geysermc/common/window/CustomFormWindow.java deleted file mode 100644 index e69de29bb..000000000 diff --git a/common/src/main/java/org/geysermc/common/window/SimpleFormWindow.java b/common/src/main/java/org/geysermc/common/window/SimpleFormWindow.java deleted file mode 100644 index e69de29bb..000000000 diff --git a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java index 89eaf5395..08c1e28d3 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java +++ b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java @@ -30,14 +30,14 @@ import lombok.AllArgsConstructor; import lombok.Getter; /** - * This class contains the raw data send by Geyser to Floodgate or from Floodgate to Floodgate. - * This class is only used internally, and you should look at FloodgatePlayer instead - * (FloodgatePlayer is present in the common module in the Floodgate repo) + * This class contains the raw data send by Geyser to Floodgate or from Floodgate to Floodgate. This + * class is only used internally, and you should look at FloodgatePlayer instead (FloodgatePlayer is + * present in the API module of the Floodgate repo) */ -@AllArgsConstructor(access = AccessLevel.PACKAGE) @Getter +@AllArgsConstructor(access = AccessLevel.PRIVATE) public final class BedrockData { - public static final int EXPECTED_LENGTH = 9; + public static final int EXPECTED_LENGTH = 10; private final String version; private final String username; @@ -48,22 +48,21 @@ public final class BedrockData { private final int inputMode; private final String ip; private final LinkedPlayer linkedPlayer; + private final boolean fromProxy; + private final int dataLength; - public BedrockData(String version, String username, String xuid, int deviceOs, - String languageCode, int uiProfile, int inputMode, String ip, - LinkedPlayer linkedPlayer) { - this(version, username, xuid, deviceOs, languageCode, - inputMode, uiProfile, ip, linkedPlayer, EXPECTED_LENGTH); + public static BedrockData of(String version, String username, String xuid, int deviceOs, + String languageCode, int uiProfile, int inputMode, String ip, + LinkedPlayer linkedPlayer, boolean fromProxy) { + return new BedrockData(version, username, xuid, deviceOs, languageCode, inputMode, + uiProfile, ip, linkedPlayer, fromProxy, EXPECTED_LENGTH); } - public BedrockData(String version, String username, String xuid, int deviceOs, - String languageCode, int uiProfile, int inputMode, String ip) { - this(version, username, xuid, deviceOs, languageCode, uiProfile, inputMode, ip, null); - } - - public boolean hasPlayerLink() { - return linkedPlayer != null; + public static BedrockData of(String version, String username, String xuid, int deviceOs, + String languageCode, int uiProfile, int inputMode, String ip) { + return of(version, username, xuid, deviceOs, languageCode, + uiProfile, inputMode, ip, null, false); } public static BedrockData fromString(String data) { @@ -77,19 +76,23 @@ public final class BedrockData { return new BedrockData( split[0], split[1], split[2], Integer.parseInt(split[3]), split[4], Integer.parseInt(split[5]), Integer.parseInt(split[6]), split[7], - linkedPlayer, split.length + linkedPlayer, Boolean.parseBoolean(split[9]), split.length ); } + private static BedrockData emptyData(int dataLength) { + return new BedrockData(null, null, null, -1, null, -1, -1, null, null, false, dataLength); + } + + public boolean hasPlayerLink() { + return linkedPlayer != null; + } + @Override public String toString() { // The format is the same as the order of the fields in this class return version + '\0' + username + '\0' + xuid + '\0' + deviceOs + '\0' + languageCode + '\0' + uiProfile + '\0' + inputMode + '\0' + ip + '\0' + - (linkedPlayer != null ? linkedPlayer.toString() : "null"); - } - - private static BedrockData emptyData(int dataLength) { - return new BedrockData(null, null, null, -1, null, -1, -1, null, null, dataLength); + fromProxy + '\0' + (linkedPlayer != null ? linkedPlayer.toString() : "null"); } } diff --git a/common/src/main/java/org/geysermc/floodgate/util/DeviceOs.java b/common/src/main/java/org/geysermc/floodgate/util/DeviceOs.java index da783982c..b3245ef81 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/DeviceOs.java +++ b/common/src/main/java/org/geysermc/floodgate/util/DeviceOs.java @@ -25,7 +25,6 @@ package org.geysermc.floodgate.util; -import com.fasterxml.jackson.annotation.JsonEnumDefaultValue; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; @@ -34,7 +33,6 @@ import lombok.RequiredArgsConstructor; */ @RequiredArgsConstructor(access = AccessLevel.PRIVATE) public enum DeviceOs { - @JsonEnumDefaultValue UNKNOWN("Unknown"), ANDROID("Android"), IOS("iOS"), @@ -57,6 +55,7 @@ public enum DeviceOs { /** * Get the DeviceOs instance from the identifier. + * * @param id the DeviceOs identifier * @return The DeviceOs or {@link #UNKNOWN} if the DeviceOs wasn't found */ diff --git a/common/src/main/java/org/geysermc/floodgate/util/InputMode.java b/common/src/main/java/org/geysermc/floodgate/util/InputMode.java index 4dcaa8ab3..9664e18ae 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/InputMode.java +++ b/common/src/main/java/org/geysermc/floodgate/util/InputMode.java @@ -26,10 +26,7 @@ package org.geysermc.floodgate.util; -import com.fasterxml.jackson.annotation.JsonEnumDefaultValue; - public enum InputMode { - @JsonEnumDefaultValue UNKNOWN, KEYBOARD_MOUSE, TOUCH, // I guess Touch? @@ -40,6 +37,7 @@ public enum InputMode { /** * Get the InputMode instance from the identifier. + * * @param id the InputMode identifier * @return The InputMode or {@link #UNKNOWN} if the DeviceOs wasn't found */ diff --git a/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java b/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java index 53a167f9b..c29d461d1 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java +++ b/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java @@ -26,11 +26,14 @@ package org.geysermc.floodgate.util; +import lombok.AccessLevel; import lombok.Getter; +import lombok.RequiredArgsConstructor; import java.util.UUID; @Getter +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) public final class LinkedPlayer { /** * The Java username of the linked player @@ -45,19 +48,17 @@ public final class LinkedPlayer { */ private final UUID bedrockId; /** - * If the LinkedPlayer is send from a different platform. - * For example the LinkedPlayer is from Bungee but the data has been sent to the Bukkit server. + * If the LinkedPlayer is send from a different platform. For example the LinkedPlayer is from + * Bungee but the data has been sent to the Bukkit server. */ private boolean fromDifferentPlatform = false; - public LinkedPlayer(String javaUsername, UUID javaUniqueId, UUID bedrockId) { - this.javaUsername = javaUsername; - this.javaUniqueId = javaUniqueId; - this.bedrockId = bedrockId; + public static LinkedPlayer of(String javaUsername, UUID javaUniqueId, UUID bedrockId) { + return new LinkedPlayer(javaUsername, javaUniqueId, bedrockId); } static LinkedPlayer fromString(String data) { - if (data.length() != 3) { + if (data.length() == 4) { return null; } diff --git a/common/src/main/java/org/geysermc/floodgate/util/RawSkin.java b/common/src/main/java/org/geysermc/floodgate/util/RawSkin.java index 2ff0e3fb2..152cbec5a 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/RawSkin.java +++ b/common/src/main/java/org/geysermc/floodgate/util/RawSkin.java @@ -31,31 +31,36 @@ import lombok.ToString; import java.nio.ByteBuffer; import java.util.Base64; +import static java.lang.String.format; + @AllArgsConstructor @ToString public final class RawSkin { public int width; public int height; public byte[] data; + public boolean alex; - private RawSkin() {} + private RawSkin() { + } public static RawSkin decode(byte[] data) throws InvalidFormatException { if (data == null) { return null; } - int maxEncodedLength = 4 * (((64 * 64 * 4 + 8) + 2) / 3); + int maxEncodedLength = 4 * (((64 * 64 * 4 + 9) + 2) / 3); // if the RawSkin is longer then the max Java Edition skin length if (data.length > maxEncodedLength) { - throw new InvalidFormatException( - "Encoded data cannot be longer then " + maxEncodedLength + " bytes!" - ); + throw new InvalidFormatException(format( + "Encoded data cannot be longer then %s bytes! Got %s", + maxEncodedLength, data.length + )); } - // if the encoded data doesn't even contain the width and height (8 bytes, 2 ints) - if (data.length < 4 * ((8 + 2) / 3)) { - throw new InvalidFormatException("Encoded data must be at least 12 bytes long!"); + // if the encoded data doesn't even contain the width, height (8 bytes, 2 ints) and isAlex + if (data.length < 4 * ((9 + 2) / 3)) { + throw new InvalidFormatException("Encoded data must be at least 16 bytes long!"); } data = Base64.getDecoder().decode(data); @@ -65,23 +70,25 @@ public final class RawSkin { RawSkin skin = new RawSkin(); skin.width = buffer.getInt(); skin.height = buffer.getInt(); - if (buffer.remaining() != (skin.width * skin.height * 4)) { - throw new InvalidFormatException(String.format( + if (buffer.remaining() - 1 != (skin.width * skin.height * 4)) { + throw new InvalidFormatException(format( "Expected skin length to be %s, got %s", (skin.width * skin.height * 4), buffer.remaining() )); } - skin.data = new byte[buffer.remaining()]; + skin.data = new byte[buffer.remaining() - 1]; buffer.get(skin.data); + skin.alex = buffer.get() == 1; return skin; } public byte[] encode() { - // 2 x int = 8 bytes - ByteBuffer buffer = ByteBuffer.allocate(8 + data.length); + // 2 x int + 1 = 9 bytes + ByteBuffer buffer = ByteBuffer.allocate(9 + data.length); buffer.putInt(width); buffer.putInt(height); buffer.put(data); + buffer.put((byte) (alex ? 1 : 0)); return Base64.getEncoder().encode(buffer.array()); } } diff --git a/common/src/main/java/org/geysermc/floodgate/util/UiProfile.java b/common/src/main/java/org/geysermc/floodgate/util/UiProfile.java index 441e9202a..58d615eaa 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/UiProfile.java +++ b/common/src/main/java/org/geysermc/floodgate/util/UiProfile.java @@ -26,17 +26,14 @@ package org.geysermc.floodgate.util; -import com.fasterxml.jackson.annotation.JsonEnumDefaultValue; - public enum UiProfile { - @JsonEnumDefaultValue - CLASSIC, - POCKET; + CLASSIC, POCKET; private static final UiProfile[] VALUES = values(); /** * Get the UiProfile instance from the identifier. + * * @param id the UiProfile identifier * @return The UiProfile or {@link #CLASSIC} if the UiProfile wasn't found */ diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 1c35b23de..c44f8f434 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -229,7 +229,7 @@ public class GeyserConnector { for (GeyserSession session : players) { if (session == null) continue; if (session.getClientData() == null) continue; - String os = session.getClientData().getDeviceOS().toString(); + String os = session.getClientData().getDeviceOs().toString(); if (!valueMap.containsKey(os)) { valueMap.put(os, 1); } else { diff --git a/connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java b/connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java index b229e1670..d41ad64a3 100644 --- a/connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java +++ b/connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java @@ -73,7 +73,7 @@ public class DumpInfo { this.userPlatforms = new Object2IntOpenHashMap(); for (GeyserSession session : GeyserConnector.getInstance().getPlayers()) { - DeviceOs device = session.getClientData().getDeviceOS(); + DeviceOs device = session.getClientData().getDeviceOs(); userPlatforms.put(device, userPlatforms.getOrDefault(device, 0) + 1); } 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 ede3b78bb..630a5edd9 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/FireworkEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/FireworkEntity.java @@ -67,7 +67,7 @@ public class FireworkEntity extends Entity { // TODO: Remove once Mojang fixes bugs with fireworks crashing clients on these specific devices. // https://bugs.mojang.com/browse/MCPE-89115 - if (session.getClientData().getDeviceOS() == DeviceOs.XBOX_ONE || session.getClientData().getDeviceOS() == DeviceOs.ORBIS) { + if (session.getClientData().getDeviceOs() == DeviceOs.XBOX_ONE || session.getClientData().getDeviceOs() == DeviceOs.ORBIS) { return; } 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 bea234b50..99aada2a5 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 @@ -402,11 +402,11 @@ public class GeyserSession implements CommandSender { try { FloodgateCipher cipher = connector.getCipher(); - encryptedData = cipher.encryptFromString(new BedrockData( + encryptedData = cipher.encryptFromString(BedrockData.of( clientData.getGameVersion(), authData.getName(), authData.getXboxUUID(), - clientData.getDeviceOS().ordinal(), + clientData.getDeviceOs().ordinal(), clientData.getLanguageCode(), clientData.getUiProfile().ordinal(), clientData.getCurrentInputMode().ordinal(), diff --git a/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java b/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java index ab0fad845..905245ee4 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java @@ -29,6 +29,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.base.Charsets; import lombok.Getter; import org.geysermc.connector.utils.SkinProvider; import org.geysermc.floodgate.util.DeviceOs; @@ -87,7 +88,7 @@ public final class BedrockClientData { @JsonProperty(value = "DeviceModel") private String deviceModel; @JsonProperty(value = "DeviceOS") - private DeviceOs deviceOS; + private DeviceOs deviceOs; @JsonProperty(value = "UIProfile") private UiProfile uiProfile; @JsonProperty(value = "GuiScale") @@ -114,13 +115,7 @@ public final class BedrockClientData { @JsonProperty(value = "ThirdPartyNameOnly") private boolean thirdPartyNameOnly; - public void setJsonData(JsonNode data) { - if (this.jsonData == null && data != null) { - this.jsonData = data; - } - } - - private static RawSkin getLegacyImage(byte[] imageData) { + private static RawSkin getLegacyImage(byte[] imageData, boolean alex) { if (imageData == null) { return null; } @@ -128,43 +123,54 @@ public final class BedrockClientData { // width * height * 4 (rgba) switch (imageData.length) { case 8192: - return new RawSkin(64, 32, imageData); + return new RawSkin(64, 32, imageData, alex); case 16384: - return new RawSkin(64, 64, imageData); + return new RawSkin(64, 64, imageData, alex); case 32768: - return new RawSkin(64, 128, imageData); + return new RawSkin(64, 128, imageData, alex); case 65536: - return new RawSkin(128, 128, imageData); + return new RawSkin(128, 128, imageData, alex); default: throw new IllegalArgumentException("Unknown legacy skin size"); } } + public void setJsonData(JsonNode data) { + if (this.jsonData == null && data != null) { + this.jsonData = data; + } + } + /** * Taken from https://github.com/NukkitX/Nukkit/blob/master/src/main/java/cn/nukkit/network/protocol/LoginPacket.java
* Internally only used for Skins, but can be used for Capes too */ public RawSkin getImage(String name) { - System.out.println(jsonData.toString()); if (jsonData == null || !jsonData.has(name + "Data")) { return null; } + boolean alex = false; + if (name.equals("Skin")) { + alex = isAlex(); + } + byte[] image = Base64.getDecoder().decode(jsonData.get(name + "Data").asText()); if (jsonData.has(name + "ImageWidth") && jsonData.has(name + "ImageHeight")) { return new RawSkin( jsonData.get(name + "ImageWidth").asInt(), jsonData.get(name + "ImageHeight").asInt(), - image + image, alex ); } - return getLegacyImage(image); + return getLegacyImage(image, alex); } public RawSkin getAndTransformImage(String name) { RawSkin skin = getImage(name); if (skin != null && (skin.width > 64 || skin.height > 64)) { - BufferedImage scaledImage = SkinProvider.imageDataToBufferedImage(skin.data, skin.width, skin.height); + BufferedImage scaledImage = + SkinProvider.imageDataToBufferedImage(skin.data, skin.width, skin.height); int max = Math.max(skin.width, skin.height); while (max > 64) { @@ -179,4 +185,35 @@ public final class BedrockClientData { } return skin; } + + public boolean isAlex() { + try { + byte[] bytes = Base64.getDecoder().decode(geometryName.getBytes(Charsets.UTF_8)); + String geometryName = + SkinProvider.OBJECT_MAPPER + .readTree(bytes) + .get("geometry").get("default") + .asText(); + return "geometry.humanoid.customSlim".equals(geometryName); + } catch (Exception exception) { + exception.printStackTrace(); + return false; + } + } + + public DeviceOs getDeviceOs() { + return deviceOs != null ? deviceOs : DeviceOs.UNKNOWN; + } + + public InputMode getCurrentInputMode() { + return currentInputMode != null ? currentInputMode : InputMode.UNKNOWN; + } + + public InputMode getDefaultInputMode() { + return defaultInputMode != null ? defaultInputMode : InputMode.UNKNOWN; + } + + public UiProfile getUiProfile() { + return uiProfile != null ? uiProfile : UiProfile.CLASSIC; + } } 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 a86c1a971..c0207841d 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 @@ -34,6 +34,7 @@ import com.github.steveice10.mc.protocol.packet.ingame.server.ServerJoinGamePack import com.nukkitx.protocol.bedrock.data.GameRuleData; import com.nukkitx.protocol.bedrock.data.PlayerPermission; import com.nukkitx.protocol.bedrock.packet.*; +import org.geysermc.connector.common.AuthType; import org.geysermc.connector.entity.PlayerEntity; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; @@ -46,7 +47,6 @@ import java.util.List; @Translator(packet = ServerJoinGamePacket.class) public class JavaJoinGameTranslator extends PacketTranslator { - @Override public void translate(ServerJoinGamePacket packet, GeyserSession session) { PlayerEntity entity = session.getPlayerEntity(); @@ -96,6 +96,11 @@ public class JavaJoinGameTranslator extends PacketTranslator { - @Override public void translate(ServerStatisticsPacket packet, GeyserSession session) { session.updateStatistics(packet.getStatistics()); if (session.isWaitingForStatistics()) { session.setWaitingForStatistics(false); - session.sendForm(StatisticsUtils.buildMenuForm(session), StatisticsUtils.STATISTICS_MENU_FORM_ID); + StatisticsUtils.buildAndSendStatisticsMenu(session); } } } 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 06d2936ef..fd25ce0a0 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java @@ -60,7 +60,9 @@ public class LanguageUtils { public static void loadGeyserLocale(String locale) { locale = formatLocale(locale); // Don't load the locale if it's already loaded. - if (LOCALE_MAPPINGS.containsKey(locale)) return; + if (LOCALE_MAPPINGS.containsKey(locale)) { + return; + } InputStream localeStream = GeyserConnector.class.getClassLoader().getResourceAsStream("languages/texts/" + locale + ".properties"); @@ -109,7 +111,7 @@ public class LanguageUtils { // Try and get the key from the default locale if (formatString == null) { - properties = LOCALE_MAPPINGS.get(formatLocale(getDefaultLocale())); + properties = LOCALE_MAPPINGS.get(getDefaultLocale()); formatString = properties.getProperty(key); } @@ -121,7 +123,7 @@ public class LanguageUtils { // Final fallback if (formatString == null) { - formatString = key; + return key; } return MessageFormat.format(formatString.replace("'", "''").replace("&", "\u00a7"), values); @@ -147,7 +149,10 @@ public class LanguageUtils { * @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 + 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 && 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 391a02849..84997deb6 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java @@ -35,10 +35,8 @@ import com.nukkitx.protocol.bedrock.packet.LoginPacket; import com.nukkitx.protocol.bedrock.packet.ServerToClientHandshakePacket; import com.nukkitx.protocol.bedrock.util.EncryptionUtils; import org.geysermc.common.form.CustomForm; -import org.geysermc.common.form.ModalForm; import org.geysermc.common.form.SimpleForm; import org.geysermc.common.form.response.CustomFormResponse; -import org.geysermc.common.form.response.ModalFormResponse; import org.geysermc.common.form.response.SimpleFormResponse; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; diff --git a/connector/src/main/java/org/geysermc/connector/utils/PluginMessageUtils.java b/connector/src/main/java/org/geysermc/connector/utils/PluginMessageUtils.java index eff9ee57b..4c7547d3a 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/PluginMessageUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/PluginMessageUtils.java @@ -25,27 +25,47 @@ package org.geysermc.connector.utils; +import com.google.common.base.Charsets; import org.geysermc.connector.GeyserConnector; -import java.nio.charset.StandardCharsets; +import java.nio.ByteBuffer; public class PluginMessageUtils { - private static final byte[] BRAND_DATA; + private static final byte[] GEYSER_BRAND_DATA; + private static final byte[] FLOODGATE_REGISTER_DATA; static { - byte[] data = GeyserConnector.NAME.getBytes(StandardCharsets.UTF_8); - byte[] varInt = writeVarInt(data.length); - BRAND_DATA = new byte[varInt.length + data.length]; - System.arraycopy(varInt, 0, BRAND_DATA, 0, varInt.length); - System.arraycopy(data, 0, BRAND_DATA, varInt.length, data.length); + byte[] data = GeyserConnector.NAME.getBytes(Charsets.UTF_8); + GEYSER_BRAND_DATA = + ByteBuffer.allocate(data.length + getVarIntLength(data.length)) + .put(writeVarInt(data.length)) + .put(data) + .array(); + + data = "floodgate:skin\0floodgate:form".getBytes(Charsets.UTF_8); + FLOODGATE_REGISTER_DATA = + ByteBuffer.allocate(data.length + getVarIntLength(data.length)) + .put(writeVarInt(data.length)) + .put(data) + .array(); } /** * Get the prebuilt brand as a byte array + * * @return the brand information of the Geyser client */ public static byte[] getGeyserBrandData() { - return BRAND_DATA; + return GEYSER_BRAND_DATA; + } + + /** + * Get the prebuilt register data as a byte array + * + * @return the register data of the Floodgate channels + */ + public static byte[] getFloodgateRegisterData() { + return FLOODGATE_REGISTER_DATA; } private static byte[] writeVarInt(int value) { 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 b74ecefad..530c551ab 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java @@ -77,7 +77,7 @@ public class SkinProvider { public static String EARS_GEOMETRY; public static String EARS_GEOMETRY_SLIM; - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); static { /* Load in the normal ears geometry */ @@ -525,7 +525,6 @@ public class SkinProvider { outputStream.write((rgba >> 24) & 0xFF); } } - return outputStream.toByteArray(); } diff --git a/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java b/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java index 732217c24..41084504a 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java @@ -28,196 +28,170 @@ package org.geysermc.connector.utils; import com.github.steveice10.mc.protocol.data.MagicValues; import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType; import com.github.steveice10.mc.protocol.data.game.statistic.*; -import org.geysermc.common.window.SimpleFormWindow; -import org.geysermc.common.window.button.FormButton; -import org.geysermc.common.window.response.SimpleFormResponse; +import org.geysermc.common.form.SimpleForm; +import org.geysermc.common.form.response.SimpleFormResponse; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.network.translators.world.block.BlockTranslator; import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; -public class StatisticsUtils { //todo make Geyser compilable - - // Used in UpstreamPacketHandler.java - public static final int STATISTICS_MENU_FORM_ID = 1339; - public static final int STATISTICS_LIST_FORM_ID = 1340; +public class StatisticsUtils { + private static final Pattern CONTENT_PATTERN = Pattern.compile("^\\S+:", Pattern.MULTILINE); /** * Build a form for the given session with all statistic categories * * @param session The session to build the form for */ - public static SimpleFormWindow buildMenuForm(GeyserSession session) { + public static void buildAndSendStatisticsMenu(GeyserSession session) { // Cache the language for cleaner access - String language = session.getClientData().getLanguageCode(); + String language = session.getLocale(); - SimpleFormWindow window = new SimpleFormWindow(LocaleUtils.getLocaleString("gui.stats", language), ""); + session.sendForm( + SimpleForm.builder() + .translator(StatisticsUtils::translate, language) + .title("gui.stats") + .button("stat.generalButton") + .button("stat.itemsButton - stat_type.minecraft.mined") + .button("stat.itemsButton - stat_type.minecraft.broken") + .button("stat.itemsButton - stat_type.minecraft.crafted") + .button("stat.itemsButton - stat_type.minecraft.used") + .button("stat.itemsButton - stat_type.minecraft.picked_up") + .button("stat.itemsButton - stat_type.minecraft.dropped") + .button("stat.mobsButton - geyser.statistics.killed") + .button("stat.mobsButton - geyser.statistics.killed_by") + .responseHandler((form, responseData) -> { + SimpleFormResponse response = form.parseResponse(responseData); + if (!response.isCorrect()) { + return; + } - window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.generalButton", language))); + SimpleForm.Builder builder = + SimpleForm.builder() + .translator(StatisticsUtils::translate, language); - window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.mined", language))); - window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.broken", language))); - window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.crafted", language))); - window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.used", language))); - window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.picked_up", language))); - window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.dropped", language))); + StringBuilder content = new StringBuilder(); - window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.mobsButton", language) + " - " + LanguageUtils.getPlayerLocaleString("geyser.statistics.killed", language))); - window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.mobsButton", language) + " - " + LanguageUtils.getPlayerLocaleString("geyser.statistics.killed_by", language))); + switch (response.getClickedButtonId()) { + case 0: + builder.title("stat.generalButton"); - return window; - } + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof GenericStatistic) { + String statName = ((GenericStatistic) entry.getKey()).name().toLowerCase(); + content.append("stat.minecraft.").append(statName).append(": ").append(entry.getValue()).append("\n"); + } + } + break; + case 1: + builder.title("stat.itemsButton - stat_type.minecraft.mined"); - /** - * Handle the menu form response - * - * @param session The session that sent the response - * @param response The response string to parse - * @return True if the form was parsed correctly, false if not - */ - public static boolean handleMenuForm(GeyserSession session, String response) { - SimpleFormWindow menuForm = (SimpleFormWindow) session.getWindowCache().getWindows().get(STATISTICS_MENU_FORM_ID); - menuForm.setResponse(response); - SimpleFormResponse formResponse = (SimpleFormResponse) menuForm.getResponse(); + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof BreakBlockStatistic) { + String block = BlockTranslator.JAVA_ID_TO_JAVA_IDENTIFIER_MAP.get(((BreakBlockStatistic) entry.getKey()).getId()); + block = block.replace("minecraft:", "block.minecraft."); + content.append(block).append(": ").append(entry.getValue()).append("\n"); + } + } + break; + case 2: + builder.title("stat.itemsButton - stat_type.minecraft.broken"); - // Cache the language for cleaner access - String language = session.getClientData().getLanguageCode(); + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof BreakItemStatistic) { + String item = ItemRegistry.ITEM_ENTRIES.get(((BreakItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); + content.append(getItemTranslateKey(item, language)).append(": ").append(entry.getValue()).append("\n"); + } + } + break; + case 3: + builder.title("stat.itemsButton - stat_type.minecraft.crafted"); - if (formResponse != null && formResponse.getClickedButton() != null) { - String title; - StringBuilder content = new StringBuilder(); + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof CraftItemStatistic) { + String item = ItemRegistry.ITEM_ENTRIES.get(((CraftItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); + content.append(getItemTranslateKey(item, language)).append(": ").append(entry.getValue()).append("\n"); + } + } + break; + case 4: + builder.title("stat.itemsButton - stat_type.minecraft.used"); - switch (formResponse.getClickedButtonId()) { - case 0: - title = LocaleUtils.getLocaleString("stat.generalButton", language); + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof UseItemStatistic) { + String item = ItemRegistry.ITEM_ENTRIES.get(((UseItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); + content.append(getItemTranslateKey(item, language)).append(": ").append(entry.getValue()).append("\n"); + } + } + break; + case 5: + builder.title("stat.itemsButton - stat_type.minecraft.picked_up"); - for (Map.Entry entry : session.getStatistics().entrySet()) { - if (entry.getKey() instanceof GenericStatistic) { - content.append(LocaleUtils.getLocaleString("stat.minecraft." + ((GenericStatistic) entry.getKey()).name().toLowerCase(), language) + ": " + entry.getValue() + "\n"); - } - } - break; - case 1: - title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.mined", language); + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof PickupItemStatistic) { + String item = ItemRegistry.ITEM_ENTRIES.get(((PickupItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); + content.append(getItemTranslateKey(item, language)).append(": ").append(entry.getValue()).append("\n"); + } + } + break; + case 6: + builder.title("stat.itemsButton - stat_type.minecraft.dropped"); - for (Map.Entry entry : session.getStatistics().entrySet()) { - if (entry.getKey() instanceof BreakBlockStatistic) { - String block = BlockTranslator.JAVA_ID_TO_JAVA_IDENTIFIER_MAP.get(((BreakBlockStatistic) entry.getKey()).getId()); - block = block.replace("minecraft:", "block.minecraft."); - block = LocaleUtils.getLocaleString(block, language); - content.append(block + ": " + entry.getValue() + "\n"); - } - } - break; - case 2: - title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.broken", language); + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof DropItemStatistic) { + String item = ItemRegistry.ITEM_ENTRIES.get(((DropItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); + content.append(getItemTranslateKey(item, language)).append(": ").append(entry.getValue()).append("\n"); + } + } + break; + case 7: + builder.title("stat.mobsButton - geyser.statistics.killed"); - for (Map.Entry entry : session.getStatistics().entrySet()) { - if (entry.getKey() instanceof BreakItemStatistic) { - String item = ItemRegistry.ITEM_ENTRIES.get(((BreakItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); - content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n"); - } - } - break; - case 3: - title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.crafted", language); + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof KillEntityStatistic) { + String entityName = MagicValues.key(EntityType.class, ((KillEntityStatistic) entry.getKey()).getId()).name().toLowerCase(); + content.append("entity.minecraft.").append(entityName).append(": ").append(entry.getValue()).append("\n"); + } + } + break; + case 8: + builder.title("stat.mobsButton - geyser.statistics.killed_by"); - for (Map.Entry entry : session.getStatistics().entrySet()) { - if (entry.getKey() instanceof CraftItemStatistic) { - String item = ItemRegistry.ITEM_ENTRIES.get(((CraftItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); - content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n"); - } - } - break; - case 4: - title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.used", language); + for (Map.Entry entry : session + .getStatistics().entrySet()) { + if (entry.getKey() instanceof KilledByEntityStatistic) { + String entityName = MagicValues.key(EntityType.class, ((KilledByEntityStatistic) entry.getKey()).getId()).name().toLowerCase(); + content.append("entity.minecraft.").append(entityName).append(": ").append(entry.getValue()).append("\n"); + } + } + break; + default: + return; + } - for (Map.Entry entry : session.getStatistics().entrySet()) { - if (entry.getKey() instanceof UseItemStatistic) { - String item = ItemRegistry.ITEM_ENTRIES.get(((UseItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); - content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n"); - } - } - break; - case 5: - title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.picked_up", language); + if (content.length() == 0) { + content = new StringBuilder("geyser.statistics.none"); + } - for (Map.Entry entry : session.getStatistics().entrySet()) { - if (entry.getKey() instanceof PickupItemStatistic) { - String item = ItemRegistry.ITEM_ENTRIES.get(((PickupItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); - content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n"); - } - } - break; - case 6: - title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.dropped", language); - - for (Map.Entry entry : session.getStatistics().entrySet()) { - if (entry.getKey() instanceof DropItemStatistic) { - String item = ItemRegistry.ITEM_ENTRIES.get(((DropItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); - content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n"); - } - } - break; - case 7: - title = LocaleUtils.getLocaleString("stat.mobsButton", language) + " - " + LanguageUtils.getPlayerLocaleString("geyser.statistics.killed", language); - - for (Map.Entry entry : session.getStatistics().entrySet()) { - if (entry.getKey() instanceof KillEntityStatistic) { - String mob = LocaleUtils.getLocaleString("entity.minecraft." + MagicValues.key(EntityType.class, ((KillEntityStatistic) entry.getKey()).getId()).name().toLowerCase(), language); - content.append(mob + ": " + entry.getValue() + "\n"); - } - } - break; - case 8: - title = LocaleUtils.getLocaleString("stat.mobsButton", language) + " - " + LanguageUtils.getPlayerLocaleString("geyser.statistics.killed_by", language); - - for (Map.Entry entry : session.getStatistics().entrySet()) { - if (entry.getKey() instanceof KilledByEntityStatistic) { - String mob = LocaleUtils.getLocaleString("entity.minecraft." + MagicValues.key(EntityType.class, ((KilledByEntityStatistic) entry.getKey()).getId()).name().toLowerCase(), language); - content.append(mob + ": " + entry.getValue() + "\n"); - } - } - break; - default: - return false; - } - - if (content.length() == 0) { - content = new StringBuilder(LanguageUtils.getPlayerLocaleString("geyser.statistics.none", language)); - } - - SimpleFormWindow window = new SimpleFormWindow(title, content.toString()); - window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("gui.back", language))); - session.sendForm(window, STATISTICS_LIST_FORM_ID); - } - - return true; - } - - /** - * Handle the list form response - * - * @param session The session that sent the response - * @param response The response string to parse - * @return True if the form was parsed correctly, false if not - */ - public static boolean handleListForm(GeyserSession session, String response) { - SimpleFormWindow listForm = (SimpleFormWindow) session.getWindowCache().getWindows().get(STATISTICS_LIST_FORM_ID); - listForm.setResponse(response); - - if (!listForm.isClosed()) { - session.sendForm(buildMenuForm(session), STATISTICS_MENU_FORM_ID); - } - - return true; + session.sendForm( + builder.content(content.toString()) + .button("gui.back") + .responseHandler((form1, responseData1) -> { + SimpleFormResponse response1 = form.parseResponse(responseData1); + if (response1.isCorrect()) { + buildAndSendStatisticsMenu(session); + } + })); + })); } /** * Finds the item translation key from the Java locale. - * - * @param item the namespaced item to search for. + * + * @param item the namespaced item to search for. * @param language the language to search in * @return the full name of the item */ @@ -230,4 +204,31 @@ public class StatisticsUtils { //todo make Geyser compilable } return translatedItem; } + + private static String translate(String keys, String locale) { + Matcher matcher = CONTENT_PATTERN.matcher(keys); + + StringBuffer buffer = new StringBuffer(); + while (matcher.find()) { + String group = matcher.group(); + matcher.appendReplacement(buffer, translateEntry(group.substring(0, group.length() - 1), locale) + ":"); + } + + if (buffer.length() != 0) { + return matcher.appendTail(buffer).toString(); + } + + String[] keySplitted = keys.split(" - "); + for (int i = 0; i < keySplitted.length; i++) { + keySplitted[i] = translateEntry(keySplitted[i], locale); + } + return String.join(" - ", keySplitted); + } + + private static String translateEntry(String key, String locale) { + if (key.startsWith("geyser.")) { + return LanguageUtils.getPlayerLocaleString(key, locale); + } + return LocaleUtils.getLocaleString(key, locale); + } } From 8b811b43fb1315b0c43cedd044897a300beee486 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sat, 21 Nov 2020 02:48:15 +0100 Subject: [PATCH 012/107] Fixed mistake in LinkedPlayer --- .../main/java/org/geysermc/floodgate/util/LinkedPlayer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java b/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java index c29d461d1..1e8d67c27 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java +++ b/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java @@ -58,11 +58,11 @@ public final class LinkedPlayer { } static LinkedPlayer fromString(String data) { - if (data.length() == 4) { + String[] split = data.split(";"); + if (split.length != 3) { return null; } - String[] split = data.split(";"); LinkedPlayer player = new LinkedPlayer( split[0], UUID.fromString(split[1]), UUID.fromString(split[2]) ); From e583abffdf6cf692a51a579fd6051321af5408db Mon Sep 17 00:00:00 2001 From: Tim203 Date: Thu, 26 Nov 2020 23:00:43 +0100 Subject: [PATCH 013/107] Split Forms into an Api and an implementation --- .../org/geysermc/common/PlatformType.java | 1 - .../org/geysermc/common/form/CustomForm.java | 151 +++---------- .../java/org/geysermc/common/form/Form.java | 156 ++++--------- .../org/geysermc/common/form/FormBuilder.java | 44 ++++ .../org/geysermc/common/form/FormImage.java | 62 ++++++ .../org/geysermc/common/form/FormType.java | 51 +++++ .../java/org/geysermc/common/form/Forms.java | 46 ++++ .../org/geysermc/common/form/ModalForm.java | 76 +------ .../org/geysermc/common/form/SimpleForm.java | 90 ++------ .../form/component/ButtonComponent.java | 27 +-- .../common/form/component/Component.java | 51 +---- .../common/form/component/ComponentType.java | 61 ++++++ .../form/component/DropdownComponent.java | 71 ++---- .../common/form/component/InputComponent.java | 30 +-- .../common/form/component/LabelComponent.java | 13 +- .../form/component/SliderComponent.java | 53 ++--- .../form/component/StepSliderComponent.java | 85 ++----- .../form/component/ToggleComponent.java | 22 +- .../common/form/impl/CustomFormImpl.java | 179 +++++++++++++++ .../geysermc/common/form/impl/FormImpl.java | 121 ++++++++++ .../common/form/impl/ModalFormImpl.java | 105 +++++++++ .../common/form/impl/SimpleFormImpl.java | 120 ++++++++++ .../impl/component/ButtonComponentImpl.java | 52 +++++ .../common/form/impl/component/Component.java | 45 ++++ .../impl/component/DropdownComponentImpl.java | 102 +++++++++ .../impl/component/InputComponentImpl.java | 56 +++++ .../impl/component/LabelComponentImpl.java | 41 ++++ .../impl/component/SliderComponentImpl.java | 76 +++++++ .../component/StepSliderComponentImpl.java | 118 ++++++++++ .../impl/component/ToggleComponentImpl.java | 52 +++++ .../impl/response/CustomFormResponseImpl.java | 207 ++++++++++++++++++ .../impl/response/ModalFormResponseImpl.java | 62 ++++++ .../impl/response/SimpleFormResponseImpl.java | 62 ++++++ .../form/{ => impl}/util/FormAdaptor.java | 51 +++-- .../util/FormImageImpl.java} | 28 +-- .../form/response/CustomFormResponse.java | 163 ++------------ .../form/response/ModalFormResponse.java | 34 +-- .../form/response/SimpleFormResponse.java | 27 +-- .../network/session/GeyserSession.java | 7 +- .../network/session/cache/FormCache.java | 20 +- .../BedrockNetworkStackLatencyTranslator.java | 31 ++- .../java/JavaPluginMessageTranslator.java | 6 +- .../connector/utils/SettingsUtils.java | 2 +- 43 files changed, 1965 insertions(+), 892 deletions(-) create mode 100644 common/src/main/java/org/geysermc/common/form/FormBuilder.java create mode 100644 common/src/main/java/org/geysermc/common/form/FormImage.java create mode 100644 common/src/main/java/org/geysermc/common/form/FormType.java create mode 100644 common/src/main/java/org/geysermc/common/form/Forms.java create mode 100644 common/src/main/java/org/geysermc/common/form/component/ComponentType.java create mode 100644 common/src/main/java/org/geysermc/common/form/impl/CustomFormImpl.java create mode 100644 common/src/main/java/org/geysermc/common/form/impl/FormImpl.java create mode 100644 common/src/main/java/org/geysermc/common/form/impl/ModalFormImpl.java create mode 100644 common/src/main/java/org/geysermc/common/form/impl/SimpleFormImpl.java create mode 100644 common/src/main/java/org/geysermc/common/form/impl/component/ButtonComponentImpl.java create mode 100644 common/src/main/java/org/geysermc/common/form/impl/component/Component.java create mode 100644 common/src/main/java/org/geysermc/common/form/impl/component/DropdownComponentImpl.java create mode 100644 common/src/main/java/org/geysermc/common/form/impl/component/InputComponentImpl.java create mode 100644 common/src/main/java/org/geysermc/common/form/impl/component/LabelComponentImpl.java create mode 100644 common/src/main/java/org/geysermc/common/form/impl/component/SliderComponentImpl.java create mode 100644 common/src/main/java/org/geysermc/common/form/impl/component/StepSliderComponentImpl.java create mode 100644 common/src/main/java/org/geysermc/common/form/impl/component/ToggleComponentImpl.java create mode 100644 common/src/main/java/org/geysermc/common/form/impl/response/CustomFormResponseImpl.java create mode 100644 common/src/main/java/org/geysermc/common/form/impl/response/ModalFormResponseImpl.java create mode 100644 common/src/main/java/org/geysermc/common/form/impl/response/SimpleFormResponseImpl.java rename common/src/main/java/org/geysermc/common/form/{ => impl}/util/FormAdaptor.java (71%) rename common/src/main/java/org/geysermc/common/form/{util/FormImage.java => impl/util/FormImageImpl.java} (72%) diff --git a/common/src/main/java/org/geysermc/common/PlatformType.java b/common/src/main/java/org/geysermc/common/PlatformType.java index 883490208..e3770f299 100644 --- a/common/src/main/java/org/geysermc/common/PlatformType.java +++ b/common/src/main/java/org/geysermc/common/PlatformType.java @@ -31,7 +31,6 @@ import lombok.Getter; @Getter @AllArgsConstructor public enum PlatformType { - ANDROID("Android"), BUNGEECORD("BungeeCord"), FABRIC("Fabric"), diff --git a/common/src/main/java/org/geysermc/common/form/CustomForm.java b/common/src/main/java/org/geysermc/common/form/CustomForm.java index 5e8d93252..41097b60e 100644 --- a/common/src/main/java/org/geysermc/common/form/CustomForm.java +++ b/common/src/main/java/org/geysermc/common/form/CustomForm.java @@ -25,153 +25,62 @@ package org.geysermc.common.form; -import com.google.gson.annotations.JsonAdapter; -import lombok.Getter; -import org.geysermc.common.form.component.*; +import org.geysermc.common.form.component.Component; +import org.geysermc.common.form.component.DropdownComponent; +import org.geysermc.common.form.component.StepSliderComponent; +import org.geysermc.common.form.impl.CustomFormImpl; import org.geysermc.common.form.response.CustomFormResponse; -import org.geysermc.common.form.util.FormAdaptor; -import org.geysermc.common.form.util.FormImage; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; -@Getter -@JsonAdapter(FormAdaptor.class) -public final class CustomForm extends Form { - private final String title; - private final FormImage icon; - private final List content; - - private CustomForm(String title, FormImage icon, List content) { - super(Form.Type.CUSTOM_FORM); - - this.title = title; - this.icon = icon; - this.content = Collections.unmodifiableList(content); +public interface CustomForm extends Form { + static CustomForm.Builder builder() { + return new CustomFormImpl.Builder(); } - public static Builder builder() { - return new Builder(); + static CustomForm of(String title, FormImage icon, List content) { + return CustomFormImpl.of(title, icon, content); } - public static CustomForm of(String title, FormImage icon, List content) { - return new CustomForm(title, icon, content); - } + interface Builder extends FormBuilder { + Builder icon(FormImage.Type type, String data); - public CustomFormResponse parseResponse(String data) { - if (isClosed(data)) { - return CustomFormResponse.closed(); - } - return CustomFormResponse.of(this, data); - } + Builder iconPath(String path); - public static final class Builder extends Form.Builder { - private final List components = new ArrayList<>(); - private FormImage icon; + Builder iconUrl(String url); - public Builder icon(FormImage.Type type, String data) { - icon = FormImage.of(type, data); - return this; - } + Builder component(Component component); - public Builder iconPath(String path) { - return icon(FormImage.Type.PATH, path); - } + Builder dropdown(DropdownComponent.Builder dropdownBuilder); - public Builder iconUrl(String url) { - return icon(FormImage.Type.URL, url); - } + Builder dropdown(String text, int defaultOption, String... options); - public Builder component(Component component) { - components.add(component); - return this; - } + Builder dropdown(String text, String... options); - public Builder dropdown(DropdownComponent.Builder dropdownBuilder) { - return component(dropdownBuilder.translateAndBuild(this::translate)); - } + Builder input(String text, String placeholder, String defaultText); - public Builder dropdown(String text, int defaultOption, String... options) { - List optionsList = new ArrayList<>(); - for (String option : options) { - optionsList.add(translate(option)); - } - return component(DropdownComponent.of(translate(text), optionsList, defaultOption)); - } + Builder input(String text, String placeholder); - public Builder dropdown(String text, String... options) { - return dropdown(text, -1, options); - } + Builder input(String text); - public Builder input(String text, String placeholder, String defaultText) { - return component(InputComponent.of( - translate(text), translate(placeholder), translate(defaultText) - )); - } + Builder label(String text); - public Builder input(String text, String placeholder) { - return component(InputComponent.of(translate(text), translate(placeholder))); - } + Builder slider(String text, float min, float max, int step, float defaultValue); - public Builder input(String text) { - return component(InputComponent.of(translate(text))); - } + Builder slider(String text, float min, float max, int step); - public Builder label(String text) { - return component(LabelComponent.of(translate(text))); - } + Builder slider(String text, float min, float max, float defaultValue); - public Builder slider(String text, float min, float max, int step, float defaultValue) { - return component(SliderComponent.of(text, min, max, step, defaultValue)); - } + Builder slider(String text, float min, float max); - public Builder slider(String text, float min, float max, int step) { - return slider(text, min, max, step, -1); - } + Builder stepSlider(StepSliderComponent.Builder stepSliderBuilder); - public Builder slider(String text, float min, float max, float defaultValue) { - return slider(text, min, max, -1, defaultValue); - } + Builder stepSlider(String text, int defaultStep, String... steps); - public Builder slider(String text, float min, float max) { - return slider(text, min, max, -1, -1); - } + Builder stepSlider(String text, String... steps); - public Builder stepSlider(StepSliderComponent.Builder stepSliderBuilder) { - return component(stepSliderBuilder.translateAndBuild(this::translate)); - } + Builder toggle(String text, boolean defaultValue); - public Builder stepSlider(String text, int defaultStep, String... steps) { - List stepsList = new ArrayList<>(); - for (String option : steps) { - stepsList.add(translate(option)); - } - return component(StepSliderComponent.of(translate(text), stepsList, defaultStep)); - } - - public Builder stepSlider(String text, String... steps) { - return stepSlider(text, -1, steps); - } - - public Builder toggle(String text, boolean defaultValue) { - return component(ToggleComponent.of(translate(text), defaultValue)); - } - - public Builder toggle(String text) { - return component(ToggleComponent.of(translate(text))); - } - - @Override - public CustomForm build() { - CustomForm form = of(title, icon, components); - if (biResponseHandler != null) { - form.setResponseHandler(response -> biResponseHandler.accept(form, response)); - return form; - } - - form.setResponseHandler(responseHandler); - return form; - } + Builder toggle(String text); } } diff --git a/common/src/main/java/org/geysermc/common/form/Form.java b/common/src/main/java/org/geysermc/common/form/Form.java index 5924e6896..6fd1cbc2d 100644 --- a/common/src/main/java/org/geysermc/common/form/Form.java +++ b/common/src/main/java/org/geysermc/common/form/Form.java @@ -25,123 +25,59 @@ package org.geysermc.common.form; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.annotations.SerializedName; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.Setter; import org.geysermc.common.form.response.FormResponse; -import org.geysermc.common.form.util.FormAdaptor; -import java.util.function.BiConsumer; -import java.util.function.BiFunction; import java.util.function.Consumer; -@Getter -public abstract class Form { - protected static final Gson GSON = - new GsonBuilder() - .registerTypeAdapter(ModalForm.class, new FormAdaptor()) - .create(); +/** + * Base class of all Forms. While it can be used it doesn't contain every data you could get when + * using the specific class of the form type. + * + * @param class provided by the specific form type. It understands the response data and makes + * the data easily accessible + */ +public interface Form { + /** + * Returns the form type of this specific instance. The valid form types can be found {@link + * FormType in the FormType class} + */ + FormType getType(); - private final Type type; + /** + * Returns the data that will be sent by Geyser to the Bedrock client + */ + String getJsonData(); - @Getter(AccessLevel.NONE) - protected String hardcodedJsonData = null; + /** + * Returns the handler that will be invoked once the form got a response from the Bedrock + * client + */ + Consumer getResponseHandler(); - @Setter protected Consumer responseHandler; + /** + * Sets the handler that will be invoked once the form got a response from the Bedrock client. + * This handler contains the raw data sent by the Bedrock client. See {@link + * #parseResponse(String)} if you want to turn the given data into something that's easier to + * handle. + * + * @param responseHandler the response handler + */ + void setResponseHandler(Consumer responseHandler); - public Form(Type type) { - this.type = type; - } + /** + * Parses the method into something provided by the form implementation, which will make the + * data given by the Bedrock client easier to handle. + * + * @param response the raw data given by the Bedrock client + * @return the data in an easy-to-handle class + */ + T parseResponse(String response); - public static T fromJson(String json, Class formClass) { - return GSON.fromJson(json, formClass); - } - - public String getJsonData() { - if (hardcodedJsonData != null) { - return hardcodedJsonData; - } - return GSON.toJson(this); - } - - public abstract FormResponse parseResponse(String response); - - @SuppressWarnings("unchecked") - public T parseResponseAs(String response) { - return (T) parseResponse(response); - } - - public boolean isClosed(String response) { - return response == null || response.isEmpty() || response.equalsIgnoreCase("null"); - } - - @Getter - @RequiredArgsConstructor - public enum Type { - @SerializedName("form") - SIMPLE_FORM(SimpleForm.class), - @SerializedName("modal") - MODAL_FORM(ModalForm.class), - @SerializedName("custom_form") - CUSTOM_FORM(CustomForm.class); - - private static final Type[] VALUES = values(); - private final Class typeClass; - - public static Type getByOrdinal(int ordinal) { - return ordinal < VALUES.length ? VALUES[ordinal] : null; - } - } - - public static abstract class Builder, F extends Form> { - protected String title = ""; - - protected BiFunction translationHandler = null; - protected BiConsumer biResponseHandler; - protected Consumer responseHandler; - protected String locale; - - public T title(String title) { - this.title = translate(title); - return self(); - } - - public T translator(BiFunction translator, String locale) { - this.translationHandler = translator; - this.locale = locale; - return title(title); - } - - public T translator(BiFunction translator) { - return translator(translator, locale); - } - - public T responseHandler(BiConsumer responseHandler) { - biResponseHandler = responseHandler; - return self(); - } - - public T responseHandler(Consumer responseHandler) { - this.responseHandler = responseHandler; - return self(); - } - - public abstract F build(); - - protected String translate(String text) { - if (translationHandler != null && text != null && !text.isEmpty()) { - return translationHandler.apply(text, locale); - } - return text; - } - - @SuppressWarnings("unchecked") - protected T self() { - return (T) this; - } - } + /** + * Checks if the given data by the Bedrock client is saying that the client closed the form. + * + * @param response the raw data given by the Bedrock client + * @return true if the raw data implies that the Bedrock client closed the form + */ + boolean isClosed(String response); } diff --git a/common/src/main/java/org/geysermc/common/form/FormBuilder.java b/common/src/main/java/org/geysermc/common/form/FormBuilder.java new file mode 100644 index 000000000..f5a1ce76d --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/FormBuilder.java @@ -0,0 +1,44 @@ +/* + * 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.common.form; + +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Consumer; + +public interface FormBuilder, F extends Form> { + T title(String title); + + T translator(BiFunction translator, String locale); + + T translator(BiFunction translator); + + T responseHandler(BiConsumer responseHandler); + + T responseHandler(Consumer responseHandler); + + F build(); +} diff --git a/common/src/main/java/org/geysermc/common/form/FormImage.java b/common/src/main/java/org/geysermc/common/form/FormImage.java new file mode 100644 index 000000000..f5672923f --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/FormImage.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.common.form; + +import com.google.gson.annotations.SerializedName; +import lombok.RequiredArgsConstructor; +import org.geysermc.common.form.impl.util.FormImageImpl; + +public interface FormImage { + static FormImage of(Type type, String data) { + return FormImageImpl.of(type, data); + } + + static FormImage of(String type, String data) { + return FormImageImpl.of(type, data); + } + + Type getType(); + + String getData(); + + @RequiredArgsConstructor + enum Type { + @SerializedName("path") PATH, + @SerializedName("url") URL; + + private static final Type[] VALUES = values(); + + public static Type getByName(String name) { + String upper = name.toUpperCase(); + for (Type value : VALUES) { + if (value.name().equals(upper)) { + return value; + } + } + return null; + } + } +} diff --git a/common/src/main/java/org/geysermc/common/form/FormType.java b/common/src/main/java/org/geysermc/common/form/FormType.java new file mode 100644 index 000000000..064dec4b3 --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/FormType.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.common.form; + +import com.google.gson.annotations.SerializedName; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.geysermc.common.form.impl.CustomFormImpl; +import org.geysermc.common.form.impl.ModalFormImpl; +import org.geysermc.common.form.impl.SimpleFormImpl; + +@Getter +@RequiredArgsConstructor +public enum FormType { + @SerializedName("form") + SIMPLE_FORM(SimpleFormImpl.class), + @SerializedName("modal") + MODAL_FORM(ModalFormImpl.class), + @SerializedName("custom_form") + CUSTOM_FORM(CustomFormImpl.class); + + private static final FormType[] VALUES = values(); + private final Class typeClass; + + public static FormType getByOrdinal(int ordinal) { + return ordinal < VALUES.length ? VALUES[ordinal] : null; + } +} diff --git a/common/src/main/java/org/geysermc/common/form/Forms.java b/common/src/main/java/org/geysermc/common/form/Forms.java new file mode 100644 index 000000000..1a1c0a4cb --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/Forms.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.common.form; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import org.geysermc.common.form.impl.FormImpl; +import org.geysermc.common.form.impl.util.FormAdaptor; + +public final class Forms { + private static final Gson GSON = + new GsonBuilder() + .registerTypeAdapter(FormImpl.class, new FormAdaptor()) + .create(); + + public static Gson getGson() { + return GSON; + } + + public static > T fromJson(String json, Class formClass) { + return GSON.fromJson(json, formClass); + } +} diff --git a/common/src/main/java/org/geysermc/common/form/ModalForm.java b/common/src/main/java/org/geysermc/common/form/ModalForm.java index ea58a466e..8e35bc8b0 100644 --- a/common/src/main/java/org/geysermc/common/form/ModalForm.java +++ b/common/src/main/java/org/geysermc/common/form/ModalForm.java @@ -25,79 +25,23 @@ package org.geysermc.common.form; -import com.google.gson.annotations.JsonAdapter; -import lombok.Getter; +import org.geysermc.common.form.impl.ModalFormImpl; import org.geysermc.common.form.response.ModalFormResponse; -import org.geysermc.common.form.util.FormAdaptor; -@Getter -@JsonAdapter(FormAdaptor.class) -public class ModalForm extends Form { - private final String title; - private final String content; - private final String button1; - private final String button2; - - private ModalForm(String title, String content, String button1, String button2) { - super(Type.MODAL_FORM); - - this.title = title; - this.content = content; - this.button1 = button1; - this.button2 = button2; +public interface ModalForm extends Form { + static Builder builder() { + return new ModalFormImpl.Builder(); } - public static Builder builder() { - return new Builder(); + static ModalForm of(String title, String content, String button1, String button2) { + return ModalFormImpl.of(title, content, button1, button2); } - public static ModalForm of(String title, String content, String button1, String button2) { - return new ModalForm(title, content, button1, button2); - } + interface Builder extends FormBuilder { + Builder content(String content); - public ModalFormResponse parseResponse(String data) { - if (isClosed(data)) { - return ModalFormResponse.closed(); - } + Builder button1(String button1); - if ("true".equals(data)) { - return ModalFormResponse.of(0, button1); - } else if ("false".equals(data)) { - return ModalFormResponse.of(1, button2); - } - return ModalFormResponse.invalid(); - } - - public static final class Builder extends Form.Builder { - private String content = ""; - private String button1 = ""; - private String button2 = ""; - - public Builder content(String content) { - this.content = translate(content); - return this; - } - - public Builder button1(String button1) { - this.button1 = translate(button1); - return this; - } - - public Builder button2(String button2) { - this.button2 = translate(button2); - return this; - } - - @Override - public ModalForm build() { - ModalForm form = of(title, content, button1, button2); - if (biResponseHandler != null) { - form.setResponseHandler(response -> biResponseHandler.accept(form, response)); - return form; - } - - form.setResponseHandler(responseHandler); - return form; - } + Builder button2(String button2); } } diff --git a/common/src/main/java/org/geysermc/common/form/SimpleForm.java b/common/src/main/java/org/geysermc/common/form/SimpleForm.java index 275fb8a86..dd11544b9 100644 --- a/common/src/main/java/org/geysermc/common/form/SimpleForm.java +++ b/common/src/main/java/org/geysermc/common/form/SimpleForm.java @@ -25,93 +25,37 @@ package org.geysermc.common.form; -import com.google.gson.annotations.JsonAdapter; -import lombok.Getter; import org.geysermc.common.form.component.ButtonComponent; +import org.geysermc.common.form.impl.SimpleFormImpl; import org.geysermc.common.form.response.SimpleFormResponse; -import org.geysermc.common.form.util.FormAdaptor; -import org.geysermc.common.form.util.FormImage; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; -@Getter -@JsonAdapter(FormAdaptor.class) -public final class SimpleForm extends Form { - private final String title; - private final String content; - private final List buttons; - - private SimpleForm(String title, String content, List buttons) { - super(Type.SIMPLE_FORM); - - this.title = title; - this.content = content; - this.buttons = Collections.unmodifiableList(buttons); +/** + * + */ +public interface SimpleForm extends Form { + static Builder builder() { + return new SimpleFormImpl.Builder(); } - public static Builder builder() { - return new Builder(); + static SimpleForm of(String title, String content, List buttons) { + return SimpleFormImpl.of(title, content, buttons); } - public static SimpleForm of(String title, String content, List buttons) { - return new SimpleForm(title, content, buttons); - } + String getTitle(); - public SimpleFormResponse parseResponse(String data) { - if (isClosed(data)) { - return SimpleFormResponse.closed(); - } + String getContent(); - int buttonId; - try { - buttonId = Integer.parseInt(data); - } catch (Exception exception) { - return SimpleFormResponse.invalid(); - } + List getButtons(); - if (buttonId >= buttons.size()) { - return SimpleFormResponse.invalid(); - } + interface Builder extends FormBuilder { + Builder content(String content); - return SimpleFormResponse.of(buttonId, buttons.get(buttonId)); - } + Builder button(String text, FormImage.Type type, String data); - public static final class Builder extends Form.Builder { - private final List buttons = new ArrayList<>(); - private String content = ""; + Builder button(String text, FormImage image); - public Builder content(String content) { - this.content = translate(content); - return this; - } - - public Builder button(String text, FormImage.Type type, String data) { - buttons.add(ButtonComponent.of(translate(text), type, data)); - return this; - } - - public Builder button(String text, FormImage image) { - buttons.add(ButtonComponent.of(translate(text), image)); - return this; - } - - public Builder button(String text) { - buttons.add(ButtonComponent.of(translate(text))); - return this; - } - - @Override - public SimpleForm build() { - SimpleForm form = of(title, content, buttons); - if (biResponseHandler != null) { - form.setResponseHandler(response -> biResponseHandler.accept(form, response)); - return form; - } - - form.setResponseHandler(responseHandler); - return form; - } + Builder button(String text); } } diff --git a/common/src/main/java/org/geysermc/common/form/component/ButtonComponent.java b/common/src/main/java/org/geysermc/common/form/component/ButtonComponent.java index de241e3dd..3e8f5d438 100644 --- a/common/src/main/java/org/geysermc/common/form/component/ButtonComponent.java +++ b/common/src/main/java/org/geysermc/common/form/component/ButtonComponent.java @@ -25,26 +25,23 @@ package org.geysermc.common.form.component; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import org.geysermc.common.form.util.FormImage; +import org.geysermc.common.form.FormImage; +import org.geysermc.common.form.impl.component.ButtonComponentImpl; -@Getter -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) -public final class ButtonComponent { - private final String text; - private final FormImage image; - - public static ButtonComponent of(String text, FormImage image) { - return new ButtonComponent(text, image); +public interface ButtonComponent { + static ButtonComponent of(String text, FormImage image) { + return ButtonComponentImpl.of(text, image); } - public static ButtonComponent of(String text, FormImage.Type type, String data) { - return of(text, FormImage.of(type, data)); + static ButtonComponent of(String text, FormImage.Type type, String data) { + return ButtonComponentImpl.of(text, type, data); } - public static ButtonComponent of(String text) { + static ButtonComponent of(String text) { return of(text, null); } + + String getText(); + + FormImage getImage(); } diff --git a/common/src/main/java/org/geysermc/common/form/component/Component.java b/common/src/main/java/org/geysermc/common/form/component/Component.java index 8f44a2a0a..524d2a1a9 100644 --- a/common/src/main/java/org/geysermc/common/form/component/Component.java +++ b/common/src/main/java/org/geysermc/common/form/component/Component.java @@ -25,53 +25,8 @@ package org.geysermc.common.form.component; -import com.google.gson.annotations.SerializedName; -import lombok.Getter; -import lombok.RequiredArgsConstructor; +public interface Component { + ComponentType getType(); -import java.util.Objects; - -@Getter -public abstract class Component { - private final Type type; - private final String text; - - Component(Type type, String text) { - Objects.requireNonNull(type, "Type cannot be null"); - Objects.requireNonNull(text, "Text cannot be null"); - - this.type = type; - this.text = text; - } - - @Getter - @RequiredArgsConstructor - public enum Type { - @SerializedName("dropdown") - DROPDOWN(DropdownComponent.class), - @SerializedName("input") - INPUT(InputComponent.class), - @SerializedName("label") - LABEL(LabelComponent.class), - @SerializedName("slider") - SLIDER(SliderComponent.class), - @SerializedName("step_slider") - STEP_SLIDER(StepSliderComponent.class), - @SerializedName("toggle") - TOGGLE(ToggleComponent.class); - - private static final Type[] VALUES = values(); - - private final String name = name().toLowerCase(); - private final Class componentClass; - - public static Type getByName(String name) { - for (Type type : VALUES) { - if (type.name.equals(name)) { - return type; - } - } - return null; - } - } + String getText(); } diff --git a/common/src/main/java/org/geysermc/common/form/component/ComponentType.java b/common/src/main/java/org/geysermc/common/form/component/ComponentType.java new file mode 100644 index 000000000..898e10d07 --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/component/ComponentType.java @@ -0,0 +1,61 @@ +/* + * 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.common.form.component; + +import com.google.gson.annotations.SerializedName; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum ComponentType { + @SerializedName("dropdown") + DROPDOWN(DropdownComponent.class), + @SerializedName("input") + INPUT(InputComponent.class), + @SerializedName("label") + LABEL(LabelComponent.class), + @SerializedName("slider") + SLIDER(SliderComponent.class), + @SerializedName("step_slider") + STEP_SLIDER(StepSliderComponent.class), + @SerializedName("toggle") + TOGGLE(ToggleComponent.class); + + private static final ComponentType[] VALUES = values(); + + private final String name = name().toLowerCase(); + private final Class componentClass; + + public static ComponentType getByName(String name) { + for (ComponentType type : VALUES) { + if (type.name.equals(name)) { + return type; + } + } + return null; + } +} diff --git a/common/src/main/java/org/geysermc/common/form/component/DropdownComponent.java b/common/src/main/java/org/geysermc/common/form/component/DropdownComponent.java index 6bf3f5e1a..a68b3b1e2 100644 --- a/common/src/main/java/org/geysermc/common/form/component/DropdownComponent.java +++ b/common/src/main/java/org/geysermc/common/form/component/DropdownComponent.java @@ -25,78 +25,39 @@ package org.geysermc.common.form.component; -import com.google.gson.annotations.SerializedName; -import lombok.Getter; +import org.geysermc.common.form.impl.component.DropdownComponentImpl; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.function.Function; -@Getter -public class DropdownComponent extends Component { - private final List options; - @SerializedName("default") - private final int defaultOption; - - private DropdownComponent(String text, List options, int defaultOption) { - super(Type.DROPDOWN, text); - this.options = Collections.unmodifiableList(options); - this.defaultOption = defaultOption; +public interface DropdownComponent extends Component { + static DropdownComponent of(String text, List options, int defaultOption) { + return DropdownComponentImpl.of(text, options, defaultOption); } - public static DropdownComponent of(String text, List options, int defaultOption) { - if (defaultOption == -1 || defaultOption >= options.size()) { - defaultOption = 0; - } - return new DropdownComponent(text, options, defaultOption); + static Builder builder() { + return new DropdownComponentImpl.Builder(); } - public static Builder builder() { - return new Builder(); - } - - public static Builder builder(String text) { + static Builder builder(String text) { return builder().text(text); } - public static class Builder { - private final List options = new ArrayList<>(); - private String text; - private int defaultOption = 0; + List getOptions(); - public Builder text(String text) { - this.text = text; - return this; - } + int getDefaultOption(); - public Builder option(String option, boolean isDefault) { - options.add(option); - if (isDefault) { - defaultOption = options.size() - 1; - } - return this; - } + interface Builder { + Builder text(String text); - public Builder option(String option) { - return option(option, false); - } + Builder option(String option, boolean isDefault); - public Builder defaultOption(int defaultOption) { - this.defaultOption = defaultOption; - return this; - } + Builder option(String option); - public DropdownComponent build() { - return of(text, options, defaultOption); - } + Builder defaultOption(int defaultOption); - public DropdownComponent translateAndBuild(Function translator) { - for (int i = 0; i < options.size(); i++) { - options.set(i, translator.apply(options.get(i))); - } + DropdownComponent build(); - return of(translator.apply(text), options, defaultOption); - } + DropdownComponent translateAndBuild(Function translator); } } diff --git a/common/src/main/java/org/geysermc/common/form/component/InputComponent.java b/common/src/main/java/org/geysermc/common/form/component/InputComponent.java index b5e07535f..4f054957c 100644 --- a/common/src/main/java/org/geysermc/common/form/component/InputComponent.java +++ b/common/src/main/java/org/geysermc/common/form/component/InputComponent.java @@ -25,30 +25,22 @@ package org.geysermc.common.form.component; -import com.google.gson.annotations.SerializedName; -import lombok.Getter; +import org.geysermc.common.form.impl.component.InputComponentImpl; -@Getter -public class InputComponent extends Component { - private final String placeholder; - @SerializedName("default") - private final String defaultText; - - private InputComponent(String text, String placeholder, String defaultText) { - super(Type.INPUT, text); - this.placeholder = placeholder; - this.defaultText = defaultText; +public interface InputComponent extends Component { + static InputComponent of(String text, String placeholder, String defaultText) { + return InputComponentImpl.of(text, placeholder, defaultText); } - public static InputComponent of(String text, String placeholder, String defaultText) { - return new InputComponent(text, placeholder, defaultText); + static InputComponent of(String text, String placeholder) { + return InputComponentImpl.of(text, placeholder, ""); } - public static InputComponent of(String text, String placeholder) { - return new InputComponent(text, placeholder, ""); + static InputComponent of(String text) { + return InputComponentImpl.of(text, "", ""); } - public static InputComponent of(String text) { - return new InputComponent(text, "", ""); - } + String getPlaceholder(); + + String getDefaultText(); } diff --git a/common/src/main/java/org/geysermc/common/form/component/LabelComponent.java b/common/src/main/java/org/geysermc/common/form/component/LabelComponent.java index 410f14a25..ed6f47029 100644 --- a/common/src/main/java/org/geysermc/common/form/component/LabelComponent.java +++ b/common/src/main/java/org/geysermc/common/form/component/LabelComponent.java @@ -25,15 +25,10 @@ package org.geysermc.common.form.component; -import lombok.Getter; +import org.geysermc.common.form.impl.component.LabelComponentImpl; -@Getter -public class LabelComponent extends Component { - private LabelComponent(String text) { - super(Type.LABEL, text); - } - - public static LabelComponent of(String text) { - return new LabelComponent(text); +public interface LabelComponent extends Component { + static LabelComponent of(String text) { + return LabelComponentImpl.of(text); } } diff --git a/common/src/main/java/org/geysermc/common/form/component/SliderComponent.java b/common/src/main/java/org/geysermc/common/form/component/SliderComponent.java index 0eb019e3a..9a0e3343e 100644 --- a/common/src/main/java/org/geysermc/common/form/component/SliderComponent.java +++ b/common/src/main/java/org/geysermc/common/form/component/SliderComponent.java @@ -25,49 +25,30 @@ package org.geysermc.common.form.component; -import com.google.gson.annotations.SerializedName; -import lombok.Getter; +import org.geysermc.common.form.impl.component.SliderComponentImpl; -@Getter -public final class SliderComponent extends Component { - private final float min; - private final float max; - private final int step; - @SerializedName("default") - private final float defaultValue; - - private SliderComponent(String text, float min, float max, int step, float defaultValue) { - super(Type.SLIDER, text); - this.min = min; - this.max = max; - this.step = step; - this.defaultValue = defaultValue; +public interface SliderComponent extends Component { + static SliderComponent of(String text, float min, float max, int step, float defaultValue) { + return SliderComponentImpl.of(text, min, max, step, defaultValue); } - public static SliderComponent of(String text, float min, float max, int step, float defaultValue) { - min = Math.max(min, 0f); - max = Math.max(max, min); - - if (step < 1) { - step = 1; - } - - if (defaultValue == -1f) { - defaultValue = (int) Math.floor(min + max / 2D); - } - - return new SliderComponent(text, min, max, step, defaultValue); + static SliderComponent of(String text, float min, float max, int step) { + return SliderComponentImpl.of(text, min, max, step); } - public static SliderComponent of(String text, float min, float max, int step) { - return of(text, min, max, step, -1); + static SliderComponent of(String text, float min, float max, float defaultValue) { + return SliderComponentImpl.of(text, min, max, defaultValue); } - public static SliderComponent of(String text, float min, float max, float defaultValue) { - return of(text, min, max, -1, defaultValue); + static SliderComponent of(String text, float min, float max) { + return SliderComponentImpl.of(text, min, max); } - public static SliderComponent of(String text, float min, float max) { - return of(text, min, max, -1, -1); - } + float getMin(); + + float getMax(); + + int getStep(); + + float getDefaultValue(); } diff --git a/common/src/main/java/org/geysermc/common/form/component/StepSliderComponent.java b/common/src/main/java/org/geysermc/common/form/component/StepSliderComponent.java index 4d6d8fcb2..cda4e7acb 100644 --- a/common/src/main/java/org/geysermc/common/form/component/StepSliderComponent.java +++ b/common/src/main/java/org/geysermc/common/form/component/StepSliderComponent.java @@ -25,92 +25,47 @@ package org.geysermc.common.form.component; -import com.google.gson.annotations.SerializedName; -import lombok.Getter; +import org.geysermc.common.form.impl.component.StepSliderComponentImpl; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.function.Function; -@Getter -public final class StepSliderComponent extends Component { - private final List steps; - @SerializedName("default") - private final int defaultStep; - - private StepSliderComponent(String text, List steps, int defaultStep) { - super(Type.STEP_SLIDER, text); - this.steps = Collections.unmodifiableList(steps); - this.defaultStep = defaultStep; +public interface StepSliderComponent extends Component { + static StepSliderComponent of(String text, List steps, int defaultStep) { + return StepSliderComponentImpl.of(text, steps, defaultStep); } - public static StepSliderComponent of(String text, List steps, int defaultStep) { - if (text == null) { - text = ""; - } - - if (defaultStep >= steps.size() || defaultStep == -1) { - defaultStep = 0; - } - - return new StepSliderComponent(text, steps, defaultStep); + static StepSliderComponent of(String text, int defaultStep, String... steps) { + return StepSliderComponentImpl.of(text, defaultStep, steps); } - public static StepSliderComponent of(String text, int defaultStep, String... steps) { - return of(text, Arrays.asList(steps), defaultStep); + static StepSliderComponent of(String text, String... steps) { + return StepSliderComponentImpl.of(text, steps); } - public static StepSliderComponent of(String text, String... steps) { - return of(text, 0, steps); + static Builder builder() { + return new StepSliderComponentImpl.Builder(); } - public static Builder builder() { - return new Builder(); - } - - public static Builder builder(String text) { + static Builder builder(String text) { return builder().text(text); } - public static final class Builder { - private final List steps = new ArrayList<>(); - private String text; - private int defaultStep; + List getSteps(); - public Builder text(String text) { - this.text = text; - return this; - } + int getDefaultStep(); - public Builder step(String step, boolean defaultStep) { - steps.add(step); - if (defaultStep) { - this.defaultStep = steps.size() - 1; - } - return this; - } + interface Builder { + Builder text(String text); - public Builder step(String step) { - return step(step, false); - } + Builder step(String step, boolean defaultStep); - public Builder defaultStep(int defaultStep) { - this.defaultStep = defaultStep; - return this; - } + Builder step(String step); - public StepSliderComponent build() { - return of(text, steps, defaultStep); - } + Builder defaultStep(int defaultStep); - public StepSliderComponent translateAndBuild(Function translator) { - for (int i = 0; i < steps.size(); i++) { - steps.set(i, translator.apply(steps.get(i))); - } + StepSliderComponent build(); - return of(translator.apply(text), steps, defaultStep); - } + StepSliderComponent translateAndBuild(Function translator); } } diff --git a/common/src/main/java/org/geysermc/common/form/component/ToggleComponent.java b/common/src/main/java/org/geysermc/common/form/component/ToggleComponent.java index 08341aaca..e505e995b 100644 --- a/common/src/main/java/org/geysermc/common/form/component/ToggleComponent.java +++ b/common/src/main/java/org/geysermc/common/form/component/ToggleComponent.java @@ -25,24 +25,16 @@ package org.geysermc.common.form.component; -import com.google.gson.annotations.SerializedName; -import lombok.Getter; +import org.geysermc.common.form.impl.component.ToggleComponentImpl; -@Getter -public class ToggleComponent extends Component { - @SerializedName("default") - private final boolean defaultValue; - - private ToggleComponent(String text, boolean defaultValue) { - super(Type.TOGGLE, text); - this.defaultValue = defaultValue; +public interface ToggleComponent extends Component { + static ToggleComponent of(String text, boolean defaultValue) { + return ToggleComponentImpl.of(text, defaultValue); } - public static ToggleComponent of(String text, boolean defaultValue) { - return new ToggleComponent(text, defaultValue); + static ToggleComponent of(String text) { + return ToggleComponentImpl.of(text); } - public static ToggleComponent of(String text) { - return of(text, false); - } + boolean getDefaultValue(); } diff --git a/common/src/main/java/org/geysermc/common/form/impl/CustomFormImpl.java b/common/src/main/java/org/geysermc/common/form/impl/CustomFormImpl.java new file mode 100644 index 000000000..4d1f80713 --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/impl/CustomFormImpl.java @@ -0,0 +1,179 @@ +/* + * 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.common.form.impl; + +import com.google.gson.annotations.JsonAdapter; +import lombok.Getter; +import org.geysermc.common.form.CustomForm; +import org.geysermc.common.form.FormImage; +import org.geysermc.common.form.FormType; +import org.geysermc.common.form.component.*; +import org.geysermc.common.form.impl.response.CustomFormResponseImpl; +import org.geysermc.common.form.impl.util.FormAdaptor; +import org.geysermc.common.form.impl.util.FormImageImpl; +import org.geysermc.common.form.response.CustomFormResponse; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@Getter +@JsonAdapter(FormAdaptor.class) +public final class CustomFormImpl extends FormImpl implements CustomForm { + private final String title; + private final FormImage icon; + private final List content; + + private CustomFormImpl(String title, FormImage icon, List content) { + super(FormType.CUSTOM_FORM); + + this.title = title; + this.icon = icon; + this.content = Collections.unmodifiableList(content); + } + + public static CustomFormImpl of(String title, FormImage icon, List content) { + return new CustomFormImpl(title, icon, content); + } + + public CustomFormResponseImpl parseResponse(String data) { + if (isClosed(data)) { + return CustomFormResponseImpl.closed(); + } + return CustomFormResponseImpl.of(this, data); + } + + public static final class Builder extends FormImpl.Builder + implements CustomForm.Builder { + + private final List components = new ArrayList<>(); + private FormImage icon; + + public Builder icon(FormImage.Type type, String data) { + icon = FormImageImpl.of(type, data); + return this; + } + + public Builder iconPath(String path) { + return icon(FormImage.Type.PATH, path); + } + + public Builder iconUrl(String url) { + return icon(FormImage.Type.URL, url); + } + + public Builder component(Component component) { + components.add(component); + return this; + } + + public Builder dropdown(DropdownComponent.Builder dropdownBuilder) { + return component(dropdownBuilder.translateAndBuild(this::translate)); + } + + public Builder dropdown(String text, int defaultOption, String... options) { + List optionsList = new ArrayList<>(); + for (String option : options) { + optionsList.add(translate(option)); + } + return component(DropdownComponent.of(translate(text), optionsList, defaultOption)); + } + + public Builder dropdown(String text, String... options) { + return dropdown(text, -1, options); + } + + public Builder input(String text, String placeholder, String defaultText) { + return component(InputComponent.of( + translate(text), translate(placeholder), translate(defaultText) + )); + } + + public Builder input(String text, String placeholder) { + return component(InputComponent.of(translate(text), translate(placeholder))); + } + + public Builder input(String text) { + return component(InputComponent.of(translate(text))); + } + + public Builder label(String text) { + return component(LabelComponent.of(translate(text))); + } + + public Builder slider(String text, float min, float max, int step, float defaultValue) { + return component(SliderComponent.of(text, min, max, step, defaultValue)); + } + + public Builder slider(String text, float min, float max, int step) { + return slider(text, min, max, step, -1); + } + + public Builder slider(String text, float min, float max, float defaultValue) { + return slider(text, min, max, -1, defaultValue); + } + + public Builder slider(String text, float min, float max) { + return slider(text, min, max, -1, -1); + } + + public Builder stepSlider(StepSliderComponent.Builder stepSliderBuilder) { + return component(stepSliderBuilder.translateAndBuild(this::translate)); + } + + public Builder stepSlider(String text, int defaultStep, String... steps) { + List stepsList = new ArrayList<>(); + for (String option : steps) { + stepsList.add(translate(option)); + } + return component(StepSliderComponent.of(translate(text), stepsList, defaultStep)); + } + + public Builder stepSlider(String text, String... steps) { + return stepSlider(text, -1, steps); + } + + public Builder toggle(String text, boolean defaultValue) { + return component(ToggleComponent.of(translate(text), defaultValue)); + } + + public Builder toggle(String text) { + return component(ToggleComponent.of(translate(text))); + } + + @Override + public CustomFormImpl build() { + CustomFormImpl form = of(title, icon, components); + if (biResponseHandler != null) { + form.setResponseHandler(response -> biResponseHandler.accept(form, response)); + return form; + } + + form.setResponseHandler(responseHandler); + return form; + } + } +} diff --git a/common/src/main/java/org/geysermc/common/form/impl/FormImpl.java b/common/src/main/java/org/geysermc/common/form/impl/FormImpl.java new file mode 100644 index 000000000..e9d648b8b --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/impl/FormImpl.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.common.form.impl; + +import com.google.gson.Gson; +import lombok.Getter; +import lombok.Setter; +import org.geysermc.common.form.Form; +import org.geysermc.common.form.FormBuilder; +import org.geysermc.common.form.FormType; +import org.geysermc.common.form.Forms; +import org.geysermc.common.form.response.FormResponse; + +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Consumer; + +@Getter +public abstract class FormImpl implements Form { + protected static final Gson GSON = Forms.getGson(); + + private final FormType type; + protected String hardcodedJsonData = null; + @Setter protected Consumer responseHandler; + + public FormImpl(FormType type) { + this.type = type; + } + + @Override + public String getJsonData() { + if (hardcodedJsonData != null) { + return hardcodedJsonData; + } + return GSON.toJson(this); + } + + @Override + public boolean isClosed(String response) { + return response == null || response.isEmpty() || response.equalsIgnoreCase("null"); + } + + public static abstract class Builder, F extends Form> + implements FormBuilder { + + protected String title = ""; + + protected BiFunction translationHandler = null; + protected BiConsumer biResponseHandler; + protected Consumer responseHandler; + protected String locale; + + @Override + public T title(String title) { + this.title = translate(title); + return self(); + } + + @Override + public T translator(BiFunction translator, String locale) { + this.translationHandler = translator; + this.locale = locale; + return title(title); + } + + @Override + public T translator(BiFunction translator) { + return translator(translator, locale); + } + + @Override + public T responseHandler(BiConsumer responseHandler) { + biResponseHandler = responseHandler; + return self(); + } + + @Override + public T responseHandler(Consumer responseHandler) { + this.responseHandler = responseHandler; + return self(); + } + + @Override + public abstract F build(); + + protected String translate(String text) { + if (translationHandler != null && text != null && !text.isEmpty()) { + return translationHandler.apply(text, locale); + } + return text; + } + + @SuppressWarnings("unchecked") + protected T self() { + return (T) this; + } + } +} diff --git a/common/src/main/java/org/geysermc/common/form/impl/ModalFormImpl.java b/common/src/main/java/org/geysermc/common/form/impl/ModalFormImpl.java new file mode 100644 index 000000000..b047dc59d --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/impl/ModalFormImpl.java @@ -0,0 +1,105 @@ +/* + * 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.common.form.impl; + +import com.google.gson.annotations.JsonAdapter; +import lombok.Getter; +import org.geysermc.common.form.FormType; +import org.geysermc.common.form.ModalForm; +import org.geysermc.common.form.impl.response.ModalFormResponseImpl; +import org.geysermc.common.form.impl.util.FormAdaptor; +import org.geysermc.common.form.response.ModalFormResponse; + +@Getter +@JsonAdapter(FormAdaptor.class) +public final class ModalFormImpl extends FormImpl implements ModalForm { + private final String title; + private final String content; + private final String button1; + private final String button2; + + private ModalFormImpl(String title, String content, String button1, String button2) { + super(FormType.MODAL_FORM); + + this.title = title; + this.content = content; + this.button1 = button1; + this.button2 = button2; + } + + public static ModalFormImpl of(String title, String content, String button1, String button2) { + return new ModalFormImpl(title, content, button1, button2); + } + + public ModalFormResponse parseResponse(String data) { + if (isClosed(data)) { + return ModalFormResponseImpl.closed(); + } + data = data.trim(); + + if ("true".equals(data)) { + return ModalFormResponseImpl.of(0, button1); + } else if ("false".equals(data)) { + return ModalFormResponseImpl.of(1, button2); + } + return ModalFormResponseImpl.invalid(); + } + + public static final class Builder extends FormImpl.Builder + implements ModalForm.Builder { + + private String content = ""; + private String button1 = ""; + private String button2 = ""; + + public Builder content(String content) { + this.content = translate(content); + return this; + } + + public Builder button1(String button1) { + this.button1 = translate(button1); + return this; + } + + public Builder button2(String button2) { + this.button2 = translate(button2); + return this; + } + + @Override + public ModalForm build() { + ModalFormImpl form = of(title, content, button1, button2); + if (biResponseHandler != null) { + form.setResponseHandler(response -> biResponseHandler.accept(form, response)); + return form; + } + + form.setResponseHandler(responseHandler); + return form; + } + } +} diff --git a/common/src/main/java/org/geysermc/common/form/impl/SimpleFormImpl.java b/common/src/main/java/org/geysermc/common/form/impl/SimpleFormImpl.java new file mode 100644 index 000000000..d5633ec05 --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/impl/SimpleFormImpl.java @@ -0,0 +1,120 @@ +/* + * 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.common.form.impl; + +import com.google.gson.annotations.JsonAdapter; +import lombok.Getter; +import org.geysermc.common.form.FormImage; +import org.geysermc.common.form.FormType; +import org.geysermc.common.form.SimpleForm; +import org.geysermc.common.form.component.ButtonComponent; +import org.geysermc.common.form.impl.component.ButtonComponentImpl; +import org.geysermc.common.form.impl.response.SimpleFormResponseImpl; +import org.geysermc.common.form.impl.util.FormAdaptor; +import org.geysermc.common.form.response.SimpleFormResponse; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@Getter +@JsonAdapter(FormAdaptor.class) +public final class SimpleFormImpl extends FormImpl implements SimpleForm { + private final String title; + private final String content; + private final List buttons; + + private SimpleFormImpl(String title, String content, List buttons) { + super(FormType.SIMPLE_FORM); + + this.title = title; + this.content = content; + this.buttons = Collections.unmodifiableList(buttons); + } + + public static SimpleFormImpl of(String title, String content, List buttons) { + return new SimpleFormImpl(title, content, buttons); + } + + public SimpleFormResponse parseResponse(String data) { + if (isClosed(data)) { + return SimpleFormResponseImpl.closed(); + } + data = data.trim(); + + int buttonId; + try { + buttonId = Integer.parseInt(data); + } catch (Exception exception) { + return SimpleFormResponseImpl.invalid(); + } + + if (buttonId >= buttons.size()) { + return SimpleFormResponseImpl.invalid(); + } + + return SimpleFormResponseImpl.of(buttonId, buttons.get(buttonId)); + } + + public static final class Builder extends FormImpl.Builder + implements SimpleForm.Builder { + + private final List buttons = new ArrayList<>(); + private String content = ""; + + public Builder content(String content) { + this.content = translate(content); + return this; + } + + public Builder button(String text, FormImage.Type type, String data) { + buttons.add(ButtonComponentImpl.of(translate(text), type, data)); + return this; + } + + public Builder button(String text, FormImage image) { + buttons.add(ButtonComponentImpl.of(translate(text), image)); + return this; + } + + public Builder button(String text) { + buttons.add(ButtonComponentImpl.of(translate(text))); + return this; + } + + @Override + public SimpleForm build() { + SimpleFormImpl form = of(title, content, buttons); + if (biResponseHandler != null) { + form.setResponseHandler(response -> biResponseHandler.accept(form, response)); + return form; + } + + form.setResponseHandler(responseHandler); + return form; + } + } +} diff --git a/common/src/main/java/org/geysermc/common/form/impl/component/ButtonComponentImpl.java b/common/src/main/java/org/geysermc/common/form/impl/component/ButtonComponentImpl.java new file mode 100644 index 000000000..0efac1a6d --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/impl/component/ButtonComponentImpl.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.common.form.impl.component; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.geysermc.common.form.FormImage; +import org.geysermc.common.form.component.ButtonComponent; +import org.geysermc.common.form.impl.util.FormImageImpl; + +@Getter +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public final class ButtonComponentImpl implements ButtonComponent { + private final String text; + private final FormImageImpl image; + + public static ButtonComponentImpl of(String text, FormImage image) { + return new ButtonComponentImpl(text, (FormImageImpl) image); + } + + public static ButtonComponentImpl of(String text, FormImage.Type type, String data) { + return of(text, FormImageImpl.of(type, data)); + } + + public static ButtonComponentImpl of(String text) { + return of(text, null); + } +} diff --git a/common/src/main/java/org/geysermc/common/form/impl/component/Component.java b/common/src/main/java/org/geysermc/common/form/impl/component/Component.java new file mode 100644 index 000000000..ff2f29f87 --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/impl/component/Component.java @@ -0,0 +1,45 @@ +/* + * 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.common.form.impl.component; + +import lombok.Getter; +import org.geysermc.common.form.component.ComponentType; + +import java.util.Objects; + +@Getter +public abstract class Component { + private final ComponentType type; + private final String text; + + Component(ComponentType type, String text) { + Objects.requireNonNull(type, "Type cannot be null"); + Objects.requireNonNull(text, "Text cannot be null"); + + this.type = type; + this.text = text; + } +} diff --git a/common/src/main/java/org/geysermc/common/form/impl/component/DropdownComponentImpl.java b/common/src/main/java/org/geysermc/common/form/impl/component/DropdownComponentImpl.java new file mode 100644 index 000000000..ae175ab74 --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/impl/component/DropdownComponentImpl.java @@ -0,0 +1,102 @@ +/* + * 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.common.form.impl.component; + +import com.google.gson.annotations.SerializedName; +import lombok.Getter; +import org.geysermc.common.form.component.ComponentType; +import org.geysermc.common.form.component.DropdownComponent; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.Function; + +@Getter +public final class DropdownComponentImpl extends Component implements DropdownComponent { + private final List options; + @SerializedName("default") + private final int defaultOption; + + private DropdownComponentImpl(String text, List options, int defaultOption) { + super(ComponentType.DROPDOWN, text); + this.options = Collections.unmodifiableList(options); + this.defaultOption = defaultOption; + } + + public static DropdownComponentImpl of(String text, List options, int defaultOption) { + if (defaultOption == -1 || defaultOption >= options.size()) { + defaultOption = 0; + } + return new DropdownComponentImpl(text, options, defaultOption); + } + + public static class Builder implements DropdownComponent.Builder { + private final List options = new ArrayList<>(); + private String text; + private int defaultOption = 0; + + @Override + public Builder text(String text) { + this.text = text; + return this; + } + + @Override + public Builder option(String option, boolean isDefault) { + options.add(option); + if (isDefault) { + defaultOption = options.size() - 1; + } + return this; + } + + @Override + public Builder option(String option) { + return option(option, false); + } + + @Override + public Builder defaultOption(int defaultOption) { + this.defaultOption = defaultOption; + return this; + } + + @Override + public DropdownComponentImpl build() { + return of(text, options, defaultOption); + } + + @Override + public DropdownComponentImpl translateAndBuild(Function translator) { + for (int i = 0; i < options.size(); i++) { + options.set(i, translator.apply(options.get(i))); + } + + return of(translator.apply(text), options, defaultOption); + } + } +} diff --git a/common/src/main/java/org/geysermc/common/form/impl/component/InputComponentImpl.java b/common/src/main/java/org/geysermc/common/form/impl/component/InputComponentImpl.java new file mode 100644 index 000000000..ba741304b --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/impl/component/InputComponentImpl.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.common.form.impl.component; + +import com.google.gson.annotations.SerializedName; +import lombok.Getter; +import org.geysermc.common.form.component.ComponentType; +import org.geysermc.common.form.component.InputComponent; + +@Getter +public final class InputComponentImpl extends Component implements InputComponent { + private final String placeholder; + @SerializedName("default") + private final String defaultText; + + private InputComponentImpl(String text, String placeholder, String defaultText) { + super(ComponentType.INPUT, text); + this.placeholder = placeholder; + this.defaultText = defaultText; + } + + public static InputComponentImpl of(String text, String placeholder, String defaultText) { + return new InputComponentImpl(text, placeholder, defaultText); + } + + public static InputComponentImpl of(String text, String placeholder) { + return new InputComponentImpl(text, placeholder, ""); + } + + public static InputComponentImpl of(String text) { + return new InputComponentImpl(text, "", ""); + } +} diff --git a/common/src/main/java/org/geysermc/common/form/impl/component/LabelComponentImpl.java b/common/src/main/java/org/geysermc/common/form/impl/component/LabelComponentImpl.java new file mode 100644 index 000000000..4dacb8406 --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/impl/component/LabelComponentImpl.java @@ -0,0 +1,41 @@ +/* + * 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.common.form.impl.component; + +import lombok.Getter; +import org.geysermc.common.form.component.ComponentType; +import org.geysermc.common.form.component.LabelComponent; + +@Getter +public final class LabelComponentImpl extends Component implements LabelComponent { + private LabelComponentImpl(String text) { + super(ComponentType.LABEL, text); + } + + public static LabelComponentImpl of(String text) { + return new LabelComponentImpl(text); + } +} diff --git a/common/src/main/java/org/geysermc/common/form/impl/component/SliderComponentImpl.java b/common/src/main/java/org/geysermc/common/form/impl/component/SliderComponentImpl.java new file mode 100644 index 000000000..29fc18dc8 --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/impl/component/SliderComponentImpl.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.common.form.impl.component; + +import com.google.gson.annotations.SerializedName; +import lombok.Getter; +import org.geysermc.common.form.component.ComponentType; +import org.geysermc.common.form.component.SliderComponent; + +@Getter +public final class SliderComponentImpl extends Component implements SliderComponent { + private final float min; + private final float max; + private final int step; + @SerializedName("default") + private final float defaultValue; + + private SliderComponentImpl(String text, float min, float max, int step, float defaultValue) { + super(ComponentType.SLIDER, text); + this.min = min; + this.max = max; + this.step = step; + this.defaultValue = defaultValue; + } + + public static SliderComponentImpl of(String text, float min, float max, int step, + float defaultValue) { + min = Math.max(min, 0f); + max = Math.max(max, min); + + if (step < 1) { + step = 1; + } + + if (defaultValue == -1f) { + defaultValue = (int) Math.floor(min + max / 2D); + } + + return new SliderComponentImpl(text, min, max, step, defaultValue); + } + + public static SliderComponentImpl of(String text, float min, float max, int step) { + return of(text, min, max, step, -1); + } + + public static SliderComponentImpl of(String text, float min, float max, float defaultValue) { + return of(text, min, max, -1, defaultValue); + } + + public static SliderComponentImpl of(String text, float min, float max) { + return of(text, min, max, -1, -1); + } +} diff --git a/common/src/main/java/org/geysermc/common/form/impl/component/StepSliderComponentImpl.java b/common/src/main/java/org/geysermc/common/form/impl/component/StepSliderComponentImpl.java new file mode 100644 index 000000000..dc87c54a5 --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/impl/component/StepSliderComponentImpl.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.common.form.impl.component; + +import com.google.gson.annotations.SerializedName; +import lombok.Getter; +import org.geysermc.common.form.component.ComponentType; +import org.geysermc.common.form.component.StepSliderComponent; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.function.Function; + +@Getter +public final class StepSliderComponentImpl extends Component implements StepSliderComponent { + private final List steps; + @SerializedName("default") + private final int defaultStep; + + private StepSliderComponentImpl(String text, List steps, int defaultStep) { + super(ComponentType.STEP_SLIDER, text); + this.steps = Collections.unmodifiableList(steps); + this.defaultStep = defaultStep; + } + + public static StepSliderComponentImpl of(String text, List steps, int defaultStep) { + if (text == null) { + text = ""; + } + + if (defaultStep >= steps.size() || defaultStep == -1) { + defaultStep = 0; + } + + return new StepSliderComponentImpl(text, steps, defaultStep); + } + + public static StepSliderComponentImpl of(String text, int defaultStep, String... steps) { + return of(text, Arrays.asList(steps), defaultStep); + } + + public static StepSliderComponentImpl of(String text, String... steps) { + return of(text, 0, steps); + } + + public static Builder builder() { + return new Builder(); + } + + public static Builder builder(String text) { + return builder().text(text); + } + + public static final class Builder implements StepSliderComponent.Builder { + private final List steps = new ArrayList<>(); + private String text; + private int defaultStep; + + public Builder text(String text) { + this.text = text; + return this; + } + + public Builder step(String step, boolean defaultStep) { + steps.add(step); + if (defaultStep) { + this.defaultStep = steps.size() - 1; + } + return this; + } + + public Builder step(String step) { + return step(step, false); + } + + public Builder defaultStep(int defaultStep) { + this.defaultStep = defaultStep; + return this; + } + + public StepSliderComponentImpl build() { + return of(text, steps, defaultStep); + } + + public StepSliderComponentImpl translateAndBuild(Function translator) { + for (int i = 0; i < steps.size(); i++) { + steps.set(i, translator.apply(steps.get(i))); + } + + return of(translator.apply(text), steps, defaultStep); + } + } +} diff --git a/common/src/main/java/org/geysermc/common/form/impl/component/ToggleComponentImpl.java b/common/src/main/java/org/geysermc/common/form/impl/component/ToggleComponentImpl.java new file mode 100644 index 000000000..0e29462b3 --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/impl/component/ToggleComponentImpl.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.common.form.impl.component; + +import com.google.gson.annotations.SerializedName; +import org.geysermc.common.form.component.ComponentType; +import org.geysermc.common.form.component.ToggleComponent; + +public final class ToggleComponentImpl extends Component implements ToggleComponent { + @SerializedName("default") + private final boolean defaultValue; + + private ToggleComponentImpl(String text, boolean defaultValue) { + super(ComponentType.TOGGLE, text); + this.defaultValue = defaultValue; + } + + public static ToggleComponentImpl of(String text, boolean defaultValue) { + return new ToggleComponentImpl(text, defaultValue); + } + + public static ToggleComponentImpl of(String text) { + return of(text, false); + } + + public boolean getDefaultValue() { + return defaultValue; + } +} diff --git a/common/src/main/java/org/geysermc/common/form/impl/response/CustomFormResponseImpl.java b/common/src/main/java/org/geysermc/common/form/impl/response/CustomFormResponseImpl.java new file mode 100644 index 000000000..cc0ce64b7 --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/impl/response/CustomFormResponseImpl.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.common.form.impl.response; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonPrimitive; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.ToString; +import org.geysermc.common.form.component.Component; +import org.geysermc.common.form.component.ComponentType; +import org.geysermc.common.form.impl.CustomFormImpl; +import org.geysermc.common.form.response.CustomFormResponse; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@Getter +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +@ToString +public final class CustomFormResponseImpl implements CustomFormResponse { + private static final Gson GSON = new Gson(); + private static final CustomFormResponseImpl CLOSED = + new CustomFormResponseImpl(true, false, null, null); + private static final CustomFormResponseImpl INVALID = + new CustomFormResponseImpl(false, true, null, null); + private final boolean closed; + private final boolean invalid; + + private final JsonArray responses; + private final List componentTypes; + + private int index = -1; + + public static CustomFormResponseImpl closed() { + return CLOSED; + } + + public static CustomFormResponseImpl invalid() { + return INVALID; + } + + public static CustomFormResponseImpl of(CustomFormImpl form, String responseData) { + JsonArray responses = GSON.fromJson(responseData, JsonArray.class); + List types = new ArrayList<>(); + for (Component component : form.getContent()) { + types.add(component.getType()); + } + return of(types, responses); + } + + public static CustomFormResponseImpl of(List componentTypes, + JsonArray responses) { + if (componentTypes.size() != responses.size()) { + return invalid(); + } + + return new CustomFormResponseImpl(false, false, responses, + Collections.unmodifiableList(componentTypes)); + } + + @Override + @SuppressWarnings("unchecked") + public T next(boolean includeLabels) { + if (!hasNext()) { + return null; + } + + while (++index < responses.size()) { + ComponentType type = componentTypes.get(index); + if (type == ComponentType.LABEL && !includeLabels) { + continue; + } + return (T) getDataFromType(type, index); + } + return null; // we don't have anything to check anymore + } + + @Override + public T next() { + return next(false); + } + + @Override + public void skip(int amount) { + index += amount; + } + + @Override + public void skip() { + skip(1); + } + + @Override + public void index(int index) { + this.index = index; + } + + @Override + public boolean hasNext() { + return responses.size() > index + 1; + } + + @Override + public JsonPrimitive get(int index) { + try { + return responses.get(index).getAsJsonPrimitive(); + } catch (IllegalStateException exception) { + wrongType(index, "a primitive"); + return null; + } + } + + @Override + public int getDropdown(int index) { + JsonPrimitive primitive = get(index); + if (!primitive.isNumber()) { + wrongType(index, "dropdown"); + } + return primitive.getAsInt(); + } + + @Override + public String getInput(int index) { + JsonPrimitive primitive = get(index); + if (!primitive.isString()) { + wrongType(index, "input"); + } + return primitive.getAsString(); + } + + @Override + public float getSlider(int index) { + JsonPrimitive primitive = get(index); + if (!primitive.isNumber()) { + wrongType(index, "slider"); + } + return primitive.getAsFloat(); + } + + @Override + public int getStepSlide(int index) { + JsonPrimitive primitive = get(index); + if (!primitive.isNumber()) { + wrongType(index, "step slider"); + } + return primitive.getAsInt(); + } + + @Override + public boolean getToggle(int index) { + JsonPrimitive primitive = get(index); + if (!primitive.isBoolean()) { + wrongType(index, "toggle"); + } + return primitive.getAsBoolean(); + } + + private Object getDataFromType(ComponentType type, int index) { + switch (type) { + case DROPDOWN: + return getDropdown(index); + case INPUT: + return getInput(index); + case SLIDER: + return getSlider(index); + case STEP_SLIDER: + return getStepSlide(index); + case TOGGLE: + return getToggle(index); + default: + return null; // label e.g. is always null + } + } + + private void wrongType(int index, String expected) { + throw new IllegalStateException(String.format( + "Expected %s on %s, got %s", + expected, index, responses.get(index).toString())); + } +} diff --git a/common/src/main/java/org/geysermc/common/form/impl/response/ModalFormResponseImpl.java b/common/src/main/java/org/geysermc/common/form/impl/response/ModalFormResponseImpl.java new file mode 100644 index 000000000..6d20431cd --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/impl/response/ModalFormResponseImpl.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.common.form.impl.response; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.geysermc.common.form.response.FormResponse; +import org.geysermc.common.form.response.ModalFormResponse; + +@Getter +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public final class ModalFormResponseImpl implements ModalFormResponse { + private static final ModalFormResponseImpl CLOSED = + new ModalFormResponseImpl(true, false, -1, null); + private static final ModalFormResponseImpl INVALID = + new ModalFormResponseImpl(false, true, -1, null); + private final boolean closed; + private final boolean invalid; + + private final int clickedButtonId; + private final String clickedButtonText; + + public static ModalFormResponseImpl closed() { + return CLOSED; + } + + public static ModalFormResponseImpl invalid() { + return INVALID; + } + + public static ModalFormResponseImpl of(int clickedButtonId, String clickedButtonText) { + return new ModalFormResponseImpl(false, false, clickedButtonId, clickedButtonText); + } + + public boolean getResult() { + return clickedButtonId == 0; + } +} diff --git a/common/src/main/java/org/geysermc/common/form/impl/response/SimpleFormResponseImpl.java b/common/src/main/java/org/geysermc/common/form/impl/response/SimpleFormResponseImpl.java new file mode 100644 index 000000000..24bc39366 --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/impl/response/SimpleFormResponseImpl.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.common.form.impl.response; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.geysermc.common.form.component.ButtonComponent; +import org.geysermc.common.form.response.SimpleFormResponse; + +@Getter +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public final class SimpleFormResponseImpl implements SimpleFormResponse { + private static final SimpleFormResponseImpl CLOSED = + new SimpleFormResponseImpl(true, false, -1, null); + private static final SimpleFormResponseImpl INVALID = + new SimpleFormResponseImpl(false, true, -1, null); + private final boolean closed; + private final boolean invalid; + + private final int clickedButtonId; + private final ButtonComponent clickedButton; + + public static SimpleFormResponseImpl closed() { + return CLOSED; + } + + public static SimpleFormResponseImpl invalid() { + return INVALID; + } + + public static SimpleFormResponseImpl of(int clickedButtonId, ButtonComponent clickedButton) { + return new SimpleFormResponseImpl(false, false, clickedButtonId, clickedButton); + } + + public String getClickedButtonText() { + return clickedButton.getText(); + } +} diff --git a/common/src/main/java/org/geysermc/common/form/util/FormAdaptor.java b/common/src/main/java/org/geysermc/common/form/impl/util/FormAdaptor.java similarity index 71% rename from common/src/main/java/org/geysermc/common/form/util/FormAdaptor.java rename to common/src/main/java/org/geysermc/common/form/impl/util/FormAdaptor.java index 8c30e7e8f..11d7eefe4 100644 --- a/common/src/main/java/org/geysermc/common/form/util/FormAdaptor.java +++ b/common/src/main/java/org/geysermc/common/form/impl/util/FormAdaptor.java @@ -23,28 +23,31 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.common.form.util; +package org.geysermc.common.form.impl.util; import com.google.gson.*; import com.google.gson.reflect.TypeToken; -import org.geysermc.common.form.CustomForm; -import org.geysermc.common.form.Form; -import org.geysermc.common.form.ModalForm; -import org.geysermc.common.form.SimpleForm; +import org.geysermc.common.form.FormImage; import org.geysermc.common.form.component.ButtonComponent; import org.geysermc.common.form.component.Component; +import org.geysermc.common.form.component.ComponentType; +import org.geysermc.common.form.impl.CustomFormImpl; +import org.geysermc.common.form.impl.FormImpl; +import org.geysermc.common.form.impl.ModalFormImpl; +import org.geysermc.common.form.impl.SimpleFormImpl; +import org.geysermc.common.form.impl.component.ButtonComponentImpl; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.List; -public class FormAdaptor implements JsonDeserializer, JsonSerializer { +public final class FormAdaptor implements JsonDeserializer>, JsonSerializer> { private static final Type LIST_BUTTON_TYPE = - new TypeToken>() {}.getType(); + new TypeToken>() {}.getType(); @Override - public Form deserialize(JsonElement jsonElement, Type typeOfT, - JsonDeserializationContext context) + public FormImpl deserialize(JsonElement jsonElement, Type typeOfT, + JsonDeserializationContext context) throws JsonParseException { if (!jsonElement.isJsonObject()) { @@ -52,50 +55,50 @@ public class FormAdaptor implements JsonDeserializer, JsonSerializer } JsonObject json = jsonElement.getAsJsonObject(); - if (typeOfT == SimpleForm.class) { + if (typeOfT == SimpleFormImpl.class) { String title = json.get("title").getAsString(); String content = json.get("content").getAsString(); List buttons = context .deserialize(json.get("buttons"), LIST_BUTTON_TYPE); - return SimpleForm.of(title, content, buttons); + return SimpleFormImpl.of(title, content, buttons); } - if (typeOfT == ModalForm.class) { + if (typeOfT == ModalFormImpl.class) { String title = json.get("title").getAsString(); String content = json.get("content").getAsString(); String button1 = json.get("button1").getAsString(); String button2 = json.get("button2").getAsString(); - return ModalForm.of(title, content, button1, button2); + return ModalFormImpl.of(title, content, button1, button2); } - if (typeOfT == CustomForm.class) { + if (typeOfT == CustomFormImpl.class) { String title = json.get("title").getAsString(); - FormImage icon = context.deserialize(json.get("icon"), FormImage.class); + FormImage icon = context.deserialize(json.get("icon"), FormImageImpl.class); List content = new ArrayList<>(); JsonArray contentArray = json.getAsJsonArray("content"); for (JsonElement contentElement : contentArray) { String typeName = contentElement.getAsJsonObject().get("type").getAsString(); - Component.Type type = Component.Type.getByName(typeName); + ComponentType type = ComponentType.getByName(typeName); if (type == null) { throw new JsonParseException("Failed to find Component type " + typeName); } content.add(context.deserialize(contentElement, type.getComponentClass())); } - return CustomForm.of(title, icon, content); + return CustomFormImpl.of(title, icon, content); } return null; } @Override - public JsonElement serialize(Form src, Type typeOfSrc, JsonSerializationContext context) { + public JsonElement serialize(FormImpl src, Type typeOfSrc, JsonSerializationContext context) { JsonObject result = new JsonObject(); result.add("type", context.serialize(src.getType())); - if (typeOfSrc == SimpleForm.class) { - SimpleForm form = (SimpleForm) src; + if (typeOfSrc == SimpleFormImpl.class) { + SimpleFormImpl form = (SimpleFormImpl) src; result.addProperty("title", form.getTitle()); result.addProperty("content", form.getContent()); @@ -103,8 +106,8 @@ public class FormAdaptor implements JsonDeserializer, JsonSerializer return result; } - if (typeOfSrc == ModalForm.class) { - ModalForm form = (ModalForm) src; + if (typeOfSrc == ModalFormImpl.class) { + ModalFormImpl form = (ModalFormImpl) src; result.addProperty("title", form.getTitle()); result.addProperty("content", form.getContent()); @@ -113,8 +116,8 @@ public class FormAdaptor implements JsonDeserializer, JsonSerializer return result; } - if (typeOfSrc == CustomForm.class) { - CustomForm form = (CustomForm) src; + if (typeOfSrc == CustomFormImpl.class) { + CustomFormImpl form = (CustomFormImpl) src; result.addProperty("title", form.getTitle()); result.add("icon", context.serialize(form.getIcon())); diff --git a/common/src/main/java/org/geysermc/common/form/util/FormImage.java b/common/src/main/java/org/geysermc/common/form/impl/util/FormImageImpl.java similarity index 72% rename from common/src/main/java/org/geysermc/common/form/util/FormImage.java rename to common/src/main/java/org/geysermc/common/form/impl/util/FormImageImpl.java index 876e662d0..4b8248e94 100644 --- a/common/src/main/java/org/geysermc/common/form/util/FormImage.java +++ b/common/src/main/java/org/geysermc/common/form/impl/util/FormImageImpl.java @@ -23,36 +23,24 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.common.form.util; +package org.geysermc.common.form.impl.util; import lombok.AccessLevel; import lombok.Getter; import lombok.RequiredArgsConstructor; +import org.geysermc.common.form.FormImage; @Getter @RequiredArgsConstructor(access = AccessLevel.PRIVATE) -public class FormImage { - private final String type; +public final class FormImageImpl implements FormImage { + private final Type type; private final String data; - public static FormImage of(String type, String data) { - return new FormImage(type, data); + public static FormImageImpl of(Type type, String data) { + return new FormImageImpl(type, data); } - public static FormImage of(Type type, String data) { - return of(type.getName(), data); - } - - @RequiredArgsConstructor - public enum Type { - PATH("path"), - URL("url"); - - @Getter private final String name; - - @Override - public String toString() { - return name; - } + public static FormImageImpl of(String type, String data) { + return of(Type.getByName(type), data); } } diff --git a/common/src/main/java/org/geysermc/common/form/response/CustomFormResponse.java b/common/src/main/java/org/geysermc/common/form/response/CustomFormResponse.java index 8ef50c7fb..d4d0fd99e 100644 --- a/common/src/main/java/org/geysermc/common/form/response/CustomFormResponse.java +++ b/common/src/main/java/org/geysermc/common/form/response/CustomFormResponse.java @@ -25,169 +25,38 @@ package org.geysermc.common.form.response; -import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonPrimitive; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.ToString; -import org.geysermc.common.form.CustomForm; -import org.geysermc.common.form.component.Component; +import org.geysermc.common.form.component.ComponentType; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; -@Getter -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) -@ToString -public class CustomFormResponse implements FormResponse { - private static final Gson GSON = new Gson(); - private static final CustomFormResponse CLOSED = - new CustomFormResponse(true, false, null, null); - private static final CustomFormResponse INVALID = - new CustomFormResponse(false, true, null, null); - private final boolean closed; - private final boolean invalid; +public interface CustomFormResponse extends FormResponse { + JsonArray getResponses(); - private final JsonArray responses; - private final List componentTypes; + List getComponentTypes(); - private int index = -1; + T next(boolean includeLabels); - public static CustomFormResponse closed() { - return CLOSED; - } + T next(); - public static CustomFormResponse invalid() { - return INVALID; - } + void skip(int amount); - public static CustomFormResponse of(CustomForm form, String responseData) { - JsonArray responses = GSON.fromJson(responseData, JsonArray.class); - List types = new ArrayList<>(); - for (Component component : form.getContent()) { - types.add(component.getType()); - } - return of(types, responses); - } + void skip(); - public static CustomFormResponse of(List componentTypes, - JsonArray responses) { - if (componentTypes.size() != responses.size()) { - return invalid(); - } + void index(int index); - return new CustomFormResponse(false, false, responses, - Collections.unmodifiableList(componentTypes)); - } + boolean hasNext(); - @SuppressWarnings("unchecked") - public T next(boolean includeLabels) { - if (!hasNext()) { - return null; - } + JsonPrimitive get(int index); - while (++index < responses.size()) { - Component.Type type = componentTypes.get(index); - if (type == Component.Type.LABEL && !includeLabels) { - continue; - } - return (T) getDataFromType(type, index); - } - return null; // we don't have anything to check anymore - } + int getDropdown(int index); - public T next() { - return next(false); - } + String getInput(int index); - public void skip(int amount) { - index += amount; - } + float getSlider(int index); - public void skip() { - skip(1); - } + int getStepSlide(int index); - public void index(int index) { - this.index = index; - } - - public boolean hasNext() { - return responses.size() > index + 1; - } - - public JsonPrimitive get(int index) { - try { - return responses.get(index).getAsJsonPrimitive(); - } catch (IllegalStateException exception) { - wrongType(index, "a primitive"); - return null; - } - } - - public int getDropdown(int index) { - JsonPrimitive primitive = get(index); - if (!primitive.isNumber()) { - wrongType(index, "dropdown"); - } - return primitive.getAsInt(); - } - - public String getInput(int index) { - JsonPrimitive primitive = get(index); - if (!primitive.isString()) { - wrongType(index, "input"); - } - return primitive.getAsString(); - } - - public float getSlider(int index) { - JsonPrimitive primitive = get(index); - if (!primitive.isNumber()) { - wrongType(index, "slider"); - } - return primitive.getAsFloat(); - } - - public int getStepSlide(int index) { - JsonPrimitive primitive = get(index); - if (!primitive.isNumber()) { - wrongType(index, "step slider"); - } - return primitive.getAsInt(); - } - - public boolean getToggle(int index) { - JsonPrimitive primitive = get(index); - if (!primitive.isBoolean()) { - wrongType(index, "toggle"); - } - return primitive.getAsBoolean(); - } - - private Object getDataFromType(Component.Type type, int index) { - switch (type) { - case DROPDOWN: - return getDropdown(index); - case INPUT: - return getInput(index); - case SLIDER: - return getSlider(index); - case STEP_SLIDER: - return getStepSlide(index); - case TOGGLE: - return getToggle(index); - default: - return null; // label e.g. is always null - } - } - - private void wrongType(int index, String expected) { - throw new IllegalStateException(String.format( - "Expected %s on %s, got %s", - expected, index, responses.get(index).toString())); - } + boolean getToggle(int index); } diff --git a/common/src/main/java/org/geysermc/common/form/response/ModalFormResponse.java b/common/src/main/java/org/geysermc/common/form/response/ModalFormResponse.java index b0e4de883..6e594e0ee 100644 --- a/common/src/main/java/org/geysermc/common/form/response/ModalFormResponse.java +++ b/common/src/main/java/org/geysermc/common/form/response/ModalFormResponse.java @@ -25,36 +25,10 @@ package org.geysermc.common.form.response; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; +public interface ModalFormResponse extends FormResponse { + int getClickedButtonId(); -@Getter -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) -public final class ModalFormResponse implements FormResponse { - private static final ModalFormResponse CLOSED = - new ModalFormResponse(true, false, -1, null); - private static final ModalFormResponse INVALID = - new ModalFormResponse(false, true, -1, null); - private final boolean closed; - private final boolean invalid; + String getClickedButtonText(); - private final int clickedButtonId; - private final String clickedButtonText; - - public static ModalFormResponse closed() { - return CLOSED; - } - - public static ModalFormResponse invalid() { - return INVALID; - } - - public static ModalFormResponse of(int clickedButtonId, String clickedButtonText) { - return new ModalFormResponse(false, false, clickedButtonId, clickedButtonText); - } - - public boolean getResult() { - return clickedButtonId == 0; - } + boolean getResult(); } diff --git a/common/src/main/java/org/geysermc/common/form/response/SimpleFormResponse.java b/common/src/main/java/org/geysermc/common/form/response/SimpleFormResponse.java index 6ee819f37..528a79ed1 100644 --- a/common/src/main/java/org/geysermc/common/form/response/SimpleFormResponse.java +++ b/common/src/main/java/org/geysermc/common/form/response/SimpleFormResponse.java @@ -25,31 +25,10 @@ package org.geysermc.common.form.response; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; import org.geysermc.common.form.component.ButtonComponent; -@Getter -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) -public final class SimpleFormResponse implements FormResponse { - private static final SimpleFormResponse CLOSED = new SimpleFormResponse(true, false, -1, null); - private static final SimpleFormResponse INVALID = new SimpleFormResponse(false, true, -1, null); - private final boolean closed; - private final boolean invalid; +public interface SimpleFormResponse extends FormResponse { + int getClickedButtonId(); - private final int clickedButtonId; - private final ButtonComponent clickedButton; - - public static SimpleFormResponse closed() { - return CLOSED; - } - - public static SimpleFormResponse invalid() { - return INVALID; - } - - public static SimpleFormResponse of(int clickedButtonId, ButtonComponent clickedButton) { - return new SimpleFormResponse(false, false, clickedButtonId, clickedButton); - } + ButtonComponent getClickedButton(); } 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 d3bb2436f..5f0a844b0 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 @@ -58,13 +58,13 @@ import lombok.Getter; import lombok.NonNull; import lombok.Setter; import org.geysermc.common.form.Form; +import org.geysermc.common.form.FormBuilder; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.CommandSender; import org.geysermc.connector.common.AuthType; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.PlayerEntity; import org.geysermc.connector.inventory.PlayerInventory; -import org.geysermc.connector.network.translators.chat.MessageTranslator; import org.geysermc.connector.network.remote.RemoteServer; import org.geysermc.connector.network.session.auth.AuthData; import org.geysermc.connector.network.session.auth.BedrockClientData; @@ -72,6 +72,7 @@ import org.geysermc.connector.network.session.cache.*; 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.chat.MessageTranslator; import org.geysermc.connector.network.translators.inventory.EnchantmentInventoryTranslator; import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.utils.*; @@ -606,11 +607,11 @@ public class GeyserSession implements CommandSender { return this.upstream.getAddress(); } - public void sendForm(Form form) { + public void sendForm(Form form) { formCache.showForm(form); } - public void sendForm(Form.Builder formBuilder) { + public void sendForm(FormBuilder formBuilder) { formCache.showForm(formBuilder.build()); } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java index 20ade2869..1f112e9ce 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java @@ -27,40 +27,50 @@ package org.geysermc.connector.network.session.cache; import com.nukkitx.protocol.bedrock.packet.ModalFormRequestPacket; import com.nukkitx.protocol.bedrock.packet.ModalFormResponsePacket; +import com.nukkitx.protocol.bedrock.packet.NetworkStackLatencyPacket; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import lombok.RequiredArgsConstructor; import org.geysermc.common.form.Form; import org.geysermc.connector.network.session.GeyserSession; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; @RequiredArgsConstructor public class FormCache { private final AtomicInteger formId = new AtomicInteger(0); - private final Int2ObjectMap forms = new Int2ObjectOpenHashMap<>(); + private final Int2ObjectMap> forms = new Int2ObjectOpenHashMap<>(); private final GeyserSession session; - public int addForm(Form form) { + public int addForm(Form form) { int windowId = formId.getAndIncrement(); forms.put(windowId, form); return windowId; } - public int showForm(Form form) { + public int showForm(Form form) { int windowId = addForm(form); ModalFormRequestPacket formRequestPacket = new ModalFormRequestPacket(); formRequestPacket.setFormId(windowId); formRequestPacket.setFormData(form.getJsonData()); - session.sendUpstreamPacket(formRequestPacket); + + // Hack to fix the url image loading bug + NetworkStackLatencyPacket latencyPacket = new NetworkStackLatencyPacket(); + latencyPacket.setFromServer(true); + latencyPacket.setTimestamp(-System.currentTimeMillis()); + session.getConnector().getGeneralThreadPool().schedule(() -> + session.sendUpstreamPacket(latencyPacket), + 500, TimeUnit.MILLISECONDS); + return windowId; } public void handleResponse(ModalFormResponsePacket response) { - Form form = forms.get(response.getFormId()); + Form form = forms.get(response.getFormId()); if (form == null) { return; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockNetworkStackLatencyTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockNetworkStackLatencyTranslator.java index d480b526f..c4ddfb4c2 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockNetworkStackLatencyTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockNetworkStackLatencyTranslator.java @@ -27,9 +27,16 @@ package org.geysermc.connector.network.translators.bedrock; import com.github.steveice10.mc.protocol.packet.ingame.client.ClientKeepAlivePacket; import com.nukkitx.protocol.bedrock.packet.NetworkStackLatencyPacket; +import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket; +import org.geysermc.connector.entity.attribute.Attribute; +import org.geysermc.connector.entity.attribute.AttributeType; 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.AttributeUtils; + +import java.util.Collections; +import java.util.concurrent.TimeUnit; /** * Used to send the keep alive packet back to the server @@ -39,8 +46,26 @@ public class BedrockNetworkStackLatencyTranslator extends PacketTranslator 0) { + // The client sends a timestamp back but it's rounded and therefore unreliable when we need the exact number + ClientKeepAlivePacket keepAlivePacket = new ClientKeepAlivePacket(session.getLastKeepAliveTimestamp()); + session.sendDownstreamPacket(keepAlivePacket); + return; + } + + // Hack to fix the url image loading bug + UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket(); + attributesPacket.setRuntimeEntityId(session.getPlayerEntity().getGeyserId()); + + Attribute attribute = session.getPlayerEntity().getAttributes().get(AttributeType.EXPERIENCE_LEVEL); + if (attribute != null) { + attributesPacket.setAttributes(Collections.singletonList(AttributeUtils.getBedrockAttribute(attribute))); + } else { + attributesPacket.setAttributes(Collections.singletonList(AttributeUtils.getBedrockAttribute(AttributeType.EXPERIENCE_LEVEL.getAttribute(0)))); + } + + session.getConnector().getGeneralThreadPool().schedule( + () -> session.sendUpstreamPacket(attributesPacket), + 500, TimeUnit.MILLISECONDS); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaPluginMessageTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaPluginMessageTranslator.java index 2a791d5a2..57d64ac75 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaPluginMessageTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaPluginMessageTranslator.java @@ -29,6 +29,8 @@ import com.github.steveice10.mc.protocol.packet.ingame.client.ClientPluginMessag import com.github.steveice10.mc.protocol.packet.ingame.server.ServerPluginMessagePacket; import com.google.common.base.Charsets; import org.geysermc.common.form.Form; +import org.geysermc.common.form.FormType; +import org.geysermc.common.form.Forms; import org.geysermc.connector.common.AuthType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; @@ -53,7 +55,7 @@ public class JavaPluginMessageTranslator extends PacketTranslator form = Forms.fromJson(dataString, type.getTypeClass()); form.setResponseHandler(response -> { byte[] raw = response.getBytes(StandardCharsets.UTF_8); byte[] finalData = new byte[raw.length + 2]; diff --git a/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java b/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java index 68ca24831..d3d1c703b 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java @@ -89,7 +89,7 @@ public class SettingsUtils { } builder.responseHandler((form, responseData) -> { - CustomFormResponse response = form.parseResponseAs(responseData); + CustomFormResponse response = form.parseResponse(responseData); if (response.isClosed() || response.isInvalid()) { return; } From deae3d566d550a0420ddf27457ecf6639001935c Mon Sep 17 00:00:00 2001 From: Tim203 Date: Tue, 1 Dec 2020 19:54:51 +0100 Subject: [PATCH 014/107] Updated DeviceOs --- .../org/geysermc/floodgate/util/DeviceOs.java | 16 ++++++++-------- .../connector/entity/FireworkEntity.java | 3 ++- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/common/src/main/java/org/geysermc/floodgate/util/DeviceOs.java b/common/src/main/java/org/geysermc/floodgate/util/DeviceOs.java index b3245ef81..bbc5451cf 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/DeviceOs.java +++ b/common/src/main/java/org/geysermc/floodgate/util/DeviceOs.java @@ -34,20 +34,20 @@ import lombok.RequiredArgsConstructor; @RequiredArgsConstructor(access = AccessLevel.PRIVATE) public enum DeviceOs { UNKNOWN("Unknown"), - ANDROID("Android"), + GOOGLE("Android"), IOS("iOS"), OSX("macOS"), - FIREOS("FireOS"), + AMAZON("Amazon"), GEARVR("Gear VR"), HOLOLENS("Hololens"), - WIN10("Windows 10"), - WIN32("Windows"), + UWP("Windows 10"), + WIN32("Windows x86"), DEDICATED("Dedicated"), - ORBIS("PS4"), + TVOS("Apple TV"), + PS4("PS4"), NX("Switch"), - SWITCH("Switch"), - XBOX_ONE("Xbox One"), - WIN_PHONE("Windows Phone"); + XBOX("Xbox One"), + WINDOWS_PHONE("Windows Phone"); private static final DeviceOs[] VALUES = values(); 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 630a5edd9..7c53de8ea 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/FireworkEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/FireworkEntity.java @@ -67,7 +67,8 @@ public class FireworkEntity extends Entity { // TODO: Remove once Mojang fixes bugs with fireworks crashing clients on these specific devices. // https://bugs.mojang.com/browse/MCPE-89115 - if (session.getClientData().getDeviceOs() == DeviceOs.XBOX_ONE || session.getClientData().getDeviceOs() == DeviceOs.ORBIS) { + if (session.getClientData().getDeviceOs() == DeviceOs.XBOX + || session.getClientData().getDeviceOs() == DeviceOs.PS4) { return; } From f7d23788450416ffe9134b2782958345932e0fc9 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Tue, 1 Dec 2020 23:17:54 +0100 Subject: [PATCH 015/107] Added Floodgate to GeyserDump --- .../floodgate/util/FloodgateConfigHolder.java | 35 +++++++++++++++++++ .../connector/dump/BootstrapDumpInfo.java | 9 ++--- .../org/geysermc/connector/dump/DumpInfo.java | 30 ++++++++-------- 3 files changed, 53 insertions(+), 21 deletions(-) create mode 100644 common/src/main/java/org/geysermc/floodgate/util/FloodgateConfigHolder.java diff --git a/common/src/main/java/org/geysermc/floodgate/util/FloodgateConfigHolder.java b/common/src/main/java/org/geysermc/floodgate/util/FloodgateConfigHolder.java new file mode 100644 index 000000000..4dd175d33 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/util/FloodgateConfigHolder.java @@ -0,0 +1,35 @@ +/* + * 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.floodgate.util; + +import lombok.Getter; +import lombok.Setter; + +public class FloodgateConfigHolder { + @Getter + @Setter + private static Object config; +} diff --git a/connector/src/main/java/org/geysermc/connector/dump/BootstrapDumpInfo.java b/connector/src/main/java/org/geysermc/connector/dump/BootstrapDumpInfo.java index 585565533..1f2339c9a 100644 --- a/connector/src/main/java/org/geysermc/connector/dump/BootstrapDumpInfo.java +++ b/connector/src/main/java/org/geysermc/connector/dump/BootstrapDumpInfo.java @@ -34,8 +34,7 @@ import java.util.List; @Getter public class BootstrapDumpInfo { - - private PlatformType platform; + private final PlatformType platform; public BootstrapDumpInfo() { this.platform = GeyserConnector.getInstance().getPlatformType(); @@ -43,8 +42,7 @@ public class BootstrapDumpInfo { @Getter @AllArgsConstructor - public class PluginInfo { - + public static class PluginInfo { public boolean enabled; public String name; public String version; @@ -54,8 +52,7 @@ public class BootstrapDumpInfo { @Getter @AllArgsConstructor - public class ListenerInfo { - + public static class ListenerInfo { public String ip; public int port; } diff --git a/connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java b/connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java index d41ad64a3..fdd8ea73e 100644 --- a/connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java +++ b/connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java @@ -38,6 +38,7 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.utils.DockerCheck; import org.geysermc.connector.utils.FileUtils; import org.geysermc.floodgate.util.DeviceOs; +import org.geysermc.floodgate.util.FloodgateConfigHolder; import java.io.IOException; import java.net.InetAddress; @@ -48,30 +49,31 @@ import java.util.Properties; @Getter public class DumpInfo { - @JsonIgnore private static final long MEGABYTE = 1024L * 1024L; private final DumpInfo.VersionInfo versionInfo; private Properties gitInfo; private final GeyserConfiguration config; - private Object2IntMap userPlatforms; - private RamInfo ramInfo; + private final Object floodgateConfig; + private final Object2IntMap userPlatforms; + private final RamInfo ramInfo; private final BootstrapDumpInfo bootstrapInfo; public DumpInfo() { - this.versionInfo = new DumpInfo.VersionInfo(); + this.versionInfo = new VersionInfo(); try { this.gitInfo = new Properties(); this.gitInfo.load(FileUtils.getResource("git.properties")); - } catch (IOException ignored) { } + } catch (IOException ignored) { + } this.config = GeyserConnector.getInstance().getConfig(); - + this.floodgateConfig = FloodgateConfigHolder.getConfig(); this.ramInfo = new DumpInfo.RamInfo(); - this.userPlatforms = new Object2IntOpenHashMap(); + this.userPlatforms = new Object2IntOpenHashMap<>(); for (GeyserSession session : GeyserConnector.getInstance().getPlayers()) { DeviceOs device = session.getClientData().getDeviceOs(); userPlatforms.put(device, userPlatforms.getOrDefault(device, 0) + 1); @@ -81,8 +83,7 @@ public class DumpInfo { } @Getter - public class VersionInfo { - + public static class VersionInfo { private final String name; private final String version; private final String javaVersion; @@ -97,7 +98,8 @@ public class DumpInfo { this.name = GeyserConnector.NAME; this.version = GeyserConnector.VERSION; this.javaVersion = System.getProperty("java.version"); - this.architecture = System.getProperty("os.arch"); // Usually gives Java architecture but still may be helpful. + // Usually gives Java architecture but still may be helpful. + this.architecture = System.getProperty("os.arch"); this.operatingSystem = System.getProperty("os.name"); this.operatingSystemVersion = System.getProperty("os.version"); @@ -108,9 +110,8 @@ public class DumpInfo { @Getter public static class NetworkInfo { - - private String internalIP; private final boolean dockerCheck; + private String internalIP; NetworkInfo() { if (AsteriskSerializer.showSensitive) { @@ -123,7 +124,8 @@ public class DumpInfo { try { // Fallback to the normal way of getting the local IP this.internalIP = InetAddress.getLocalHost().getHostAddress(); - } catch (UnknownHostException ignored) { } + } catch (UnknownHostException ignored) { + } } } else { // Sometimes the internal IP is the external IP... @@ -136,7 +138,6 @@ public class DumpInfo { @Getter public static class MCInfo { - private final String bedrockVersion; private final int bedrockProtocol; private final String javaVersion; @@ -152,7 +153,6 @@ public class DumpInfo { @Getter public static class RamInfo { - private final long free; private final long total; private final long max; From 45596a87a92c9f31de3f61ffa28e1f356957d5e7 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Thu, 10 Dec 2020 22:57:48 +0100 Subject: [PATCH 016/107] Use Cumulus as form library --- common/pom.xml | 8 +- .../org/geysermc/common/form/CustomForm.java | 86 -------- .../java/org/geysermc/common/form/Form.java | 83 ------- .../org/geysermc/common/form/FormBuilder.java | 44 ---- .../org/geysermc/common/form/FormImage.java | 62 ------ .../org/geysermc/common/form/FormType.java | 51 ----- .../java/org/geysermc/common/form/Forms.java | 46 ---- .../org/geysermc/common/form/ModalForm.java | 47 ---- .../org/geysermc/common/form/SimpleForm.java | 61 ------ .../form/component/ButtonComponent.java | 47 ---- .../common/form/component/Component.java | 32 --- .../common/form/component/ComponentType.java | 61 ------ .../form/component/DropdownComponent.java | 63 ------ .../common/form/component/InputComponent.java | 46 ---- .../common/form/component/LabelComponent.java | 34 --- .../form/component/SliderComponent.java | 54 ----- .../form/component/StepSliderComponent.java | 71 ------ .../form/component/ToggleComponent.java | 40 ---- .../common/form/impl/CustomFormImpl.java | 179 --------------- .../geysermc/common/form/impl/FormImpl.java | 121 ---------- .../common/form/impl/ModalFormImpl.java | 105 --------- .../common/form/impl/SimpleFormImpl.java | 120 ---------- .../impl/component/ButtonComponentImpl.java | 52 ----- .../common/form/impl/component/Component.java | 45 ---- .../impl/component/DropdownComponentImpl.java | 102 --------- .../impl/component/InputComponentImpl.java | 56 ----- .../impl/component/LabelComponentImpl.java | 41 ---- .../impl/component/SliderComponentImpl.java | 76 ------- .../component/StepSliderComponentImpl.java | 118 ---------- .../impl/component/ToggleComponentImpl.java | 52 ----- .../impl/response/CustomFormResponseImpl.java | 207 ------------------ .../impl/response/ModalFormResponseImpl.java | 62 ------ .../impl/response/SimpleFormResponseImpl.java | 62 ------ .../common/form/impl/util/FormAdaptor.java | 129 ----------- .../common/form/impl/util/FormImageImpl.java | 46 ---- .../form/response/CustomFormResponse.java | 62 ------ .../common/form/response/FormResponse.java | 35 --- .../form/response/ModalFormResponse.java | 34 --- .../form/response/SimpleFormResponse.java | 34 --- connector/pom.xml | 1 - .../network/session/GeyserSession.java | 6 +- .../network/session/cache/FormCache.java | 14 +- ...edrockServerSettingsRequestTranslator.java | 3 +- .../java/JavaPluginMessageTranslator.java | 8 +- .../connector/utils/LoginEncryptionUtils.java | 8 +- .../connector/utils/SettingsUtils.java | 6 +- .../connector/utils/StatisticsUtils.java | 4 +- 47 files changed, 30 insertions(+), 2694 deletions(-) delete mode 100644 common/src/main/java/org/geysermc/common/form/CustomForm.java delete mode 100644 common/src/main/java/org/geysermc/common/form/Form.java delete mode 100644 common/src/main/java/org/geysermc/common/form/FormBuilder.java delete mode 100644 common/src/main/java/org/geysermc/common/form/FormImage.java delete mode 100644 common/src/main/java/org/geysermc/common/form/FormType.java delete mode 100644 common/src/main/java/org/geysermc/common/form/Forms.java delete mode 100644 common/src/main/java/org/geysermc/common/form/ModalForm.java delete mode 100644 common/src/main/java/org/geysermc/common/form/SimpleForm.java delete mode 100644 common/src/main/java/org/geysermc/common/form/component/ButtonComponent.java delete mode 100644 common/src/main/java/org/geysermc/common/form/component/Component.java delete mode 100644 common/src/main/java/org/geysermc/common/form/component/ComponentType.java delete mode 100644 common/src/main/java/org/geysermc/common/form/component/DropdownComponent.java delete mode 100644 common/src/main/java/org/geysermc/common/form/component/InputComponent.java delete mode 100644 common/src/main/java/org/geysermc/common/form/component/LabelComponent.java delete mode 100644 common/src/main/java/org/geysermc/common/form/component/SliderComponent.java delete mode 100644 common/src/main/java/org/geysermc/common/form/component/StepSliderComponent.java delete mode 100644 common/src/main/java/org/geysermc/common/form/component/ToggleComponent.java delete mode 100644 common/src/main/java/org/geysermc/common/form/impl/CustomFormImpl.java delete mode 100644 common/src/main/java/org/geysermc/common/form/impl/FormImpl.java delete mode 100644 common/src/main/java/org/geysermc/common/form/impl/ModalFormImpl.java delete mode 100644 common/src/main/java/org/geysermc/common/form/impl/SimpleFormImpl.java delete mode 100644 common/src/main/java/org/geysermc/common/form/impl/component/ButtonComponentImpl.java delete mode 100644 common/src/main/java/org/geysermc/common/form/impl/component/Component.java delete mode 100644 common/src/main/java/org/geysermc/common/form/impl/component/DropdownComponentImpl.java delete mode 100644 common/src/main/java/org/geysermc/common/form/impl/component/InputComponentImpl.java delete mode 100644 common/src/main/java/org/geysermc/common/form/impl/component/LabelComponentImpl.java delete mode 100644 common/src/main/java/org/geysermc/common/form/impl/component/SliderComponentImpl.java delete mode 100644 common/src/main/java/org/geysermc/common/form/impl/component/StepSliderComponentImpl.java delete mode 100644 common/src/main/java/org/geysermc/common/form/impl/component/ToggleComponentImpl.java delete mode 100644 common/src/main/java/org/geysermc/common/form/impl/response/CustomFormResponseImpl.java delete mode 100644 common/src/main/java/org/geysermc/common/form/impl/response/ModalFormResponseImpl.java delete mode 100644 common/src/main/java/org/geysermc/common/form/impl/response/SimpleFormResponseImpl.java delete mode 100644 common/src/main/java/org/geysermc/common/form/impl/util/FormAdaptor.java delete mode 100644 common/src/main/java/org/geysermc/common/form/impl/util/FormImageImpl.java delete mode 100644 common/src/main/java/org/geysermc/common/form/response/CustomFormResponse.java delete mode 100644 common/src/main/java/org/geysermc/common/form/response/FormResponse.java delete mode 100644 common/src/main/java/org/geysermc/common/form/response/ModalFormResponse.java delete mode 100644 common/src/main/java/org/geysermc/common/form/response/SimpleFormResponse.java diff --git a/common/pom.xml b/common/pom.xml index af5664ca1..0f4d5421b 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -11,11 +11,15 @@ common + + org.geysermc.cumulus + cumulus + 1.0-SNAPSHOT + com.google.code.gson gson - 2.8.5 - compile + 2.8.6 \ No newline at end of file diff --git a/common/src/main/java/org/geysermc/common/form/CustomForm.java b/common/src/main/java/org/geysermc/common/form/CustomForm.java deleted file mode 100644 index 41097b60e..000000000 --- a/common/src/main/java/org/geysermc/common/form/CustomForm.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form; - -import org.geysermc.common.form.component.Component; -import org.geysermc.common.form.component.DropdownComponent; -import org.geysermc.common.form.component.StepSliderComponent; -import org.geysermc.common.form.impl.CustomFormImpl; -import org.geysermc.common.form.response.CustomFormResponse; - -import java.util.List; - -public interface CustomForm extends Form { - static CustomForm.Builder builder() { - return new CustomFormImpl.Builder(); - } - - static CustomForm of(String title, FormImage icon, List content) { - return CustomFormImpl.of(title, icon, content); - } - - interface Builder extends FormBuilder { - Builder icon(FormImage.Type type, String data); - - Builder iconPath(String path); - - Builder iconUrl(String url); - - Builder component(Component component); - - Builder dropdown(DropdownComponent.Builder dropdownBuilder); - - Builder dropdown(String text, int defaultOption, String... options); - - Builder dropdown(String text, String... options); - - Builder input(String text, String placeholder, String defaultText); - - Builder input(String text, String placeholder); - - Builder input(String text); - - Builder label(String text); - - Builder slider(String text, float min, float max, int step, float defaultValue); - - Builder slider(String text, float min, float max, int step); - - Builder slider(String text, float min, float max, float defaultValue); - - Builder slider(String text, float min, float max); - - Builder stepSlider(StepSliderComponent.Builder stepSliderBuilder); - - Builder stepSlider(String text, int defaultStep, String... steps); - - Builder stepSlider(String text, String... steps); - - Builder toggle(String text, boolean defaultValue); - - Builder toggle(String text); - } -} diff --git a/common/src/main/java/org/geysermc/common/form/Form.java b/common/src/main/java/org/geysermc/common/form/Form.java deleted file mode 100644 index 6fd1cbc2d..000000000 --- a/common/src/main/java/org/geysermc/common/form/Form.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form; - -import org.geysermc.common.form.response.FormResponse; - -import java.util.function.Consumer; - -/** - * Base class of all Forms. While it can be used it doesn't contain every data you could get when - * using the specific class of the form type. - * - * @param class provided by the specific form type. It understands the response data and makes - * the data easily accessible - */ -public interface Form { - /** - * Returns the form type of this specific instance. The valid form types can be found {@link - * FormType in the FormType class} - */ - FormType getType(); - - /** - * Returns the data that will be sent by Geyser to the Bedrock client - */ - String getJsonData(); - - /** - * Returns the handler that will be invoked once the form got a response from the Bedrock - * client - */ - Consumer getResponseHandler(); - - /** - * Sets the handler that will be invoked once the form got a response from the Bedrock client. - * This handler contains the raw data sent by the Bedrock client. See {@link - * #parseResponse(String)} if you want to turn the given data into something that's easier to - * handle. - * - * @param responseHandler the response handler - */ - void setResponseHandler(Consumer responseHandler); - - /** - * Parses the method into something provided by the form implementation, which will make the - * data given by the Bedrock client easier to handle. - * - * @param response the raw data given by the Bedrock client - * @return the data in an easy-to-handle class - */ - T parseResponse(String response); - - /** - * Checks if the given data by the Bedrock client is saying that the client closed the form. - * - * @param response the raw data given by the Bedrock client - * @return true if the raw data implies that the Bedrock client closed the form - */ - boolean isClosed(String response); -} diff --git a/common/src/main/java/org/geysermc/common/form/FormBuilder.java b/common/src/main/java/org/geysermc/common/form/FormBuilder.java deleted file mode 100644 index f5a1ce76d..000000000 --- a/common/src/main/java/org/geysermc/common/form/FormBuilder.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form; - -import java.util.function.BiConsumer; -import java.util.function.BiFunction; -import java.util.function.Consumer; - -public interface FormBuilder, F extends Form> { - T title(String title); - - T translator(BiFunction translator, String locale); - - T translator(BiFunction translator); - - T responseHandler(BiConsumer responseHandler); - - T responseHandler(Consumer responseHandler); - - F build(); -} diff --git a/common/src/main/java/org/geysermc/common/form/FormImage.java b/common/src/main/java/org/geysermc/common/form/FormImage.java deleted file mode 100644 index f5672923f..000000000 --- a/common/src/main/java/org/geysermc/common/form/FormImage.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form; - -import com.google.gson.annotations.SerializedName; -import lombok.RequiredArgsConstructor; -import org.geysermc.common.form.impl.util.FormImageImpl; - -public interface FormImage { - static FormImage of(Type type, String data) { - return FormImageImpl.of(type, data); - } - - static FormImage of(String type, String data) { - return FormImageImpl.of(type, data); - } - - Type getType(); - - String getData(); - - @RequiredArgsConstructor - enum Type { - @SerializedName("path") PATH, - @SerializedName("url") URL; - - private static final Type[] VALUES = values(); - - public static Type getByName(String name) { - String upper = name.toUpperCase(); - for (Type value : VALUES) { - if (value.name().equals(upper)) { - return value; - } - } - return null; - } - } -} diff --git a/common/src/main/java/org/geysermc/common/form/FormType.java b/common/src/main/java/org/geysermc/common/form/FormType.java deleted file mode 100644 index 064dec4b3..000000000 --- a/common/src/main/java/org/geysermc/common/form/FormType.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form; - -import com.google.gson.annotations.SerializedName; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import org.geysermc.common.form.impl.CustomFormImpl; -import org.geysermc.common.form.impl.ModalFormImpl; -import org.geysermc.common.form.impl.SimpleFormImpl; - -@Getter -@RequiredArgsConstructor -public enum FormType { - @SerializedName("form") - SIMPLE_FORM(SimpleFormImpl.class), - @SerializedName("modal") - MODAL_FORM(ModalFormImpl.class), - @SerializedName("custom_form") - CUSTOM_FORM(CustomFormImpl.class); - - private static final FormType[] VALUES = values(); - private final Class typeClass; - - public static FormType getByOrdinal(int ordinal) { - return ordinal < VALUES.length ? VALUES[ordinal] : null; - } -} diff --git a/common/src/main/java/org/geysermc/common/form/Forms.java b/common/src/main/java/org/geysermc/common/form/Forms.java deleted file mode 100644 index 1a1c0a4cb..000000000 --- a/common/src/main/java/org/geysermc/common/form/Forms.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import org.geysermc.common.form.impl.FormImpl; -import org.geysermc.common.form.impl.util.FormAdaptor; - -public final class Forms { - private static final Gson GSON = - new GsonBuilder() - .registerTypeAdapter(FormImpl.class, new FormAdaptor()) - .create(); - - public static Gson getGson() { - return GSON; - } - - public static > T fromJson(String json, Class formClass) { - return GSON.fromJson(json, formClass); - } -} diff --git a/common/src/main/java/org/geysermc/common/form/ModalForm.java b/common/src/main/java/org/geysermc/common/form/ModalForm.java deleted file mode 100644 index 8e35bc8b0..000000000 --- a/common/src/main/java/org/geysermc/common/form/ModalForm.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form; - -import org.geysermc.common.form.impl.ModalFormImpl; -import org.geysermc.common.form.response.ModalFormResponse; - -public interface ModalForm extends Form { - static Builder builder() { - return new ModalFormImpl.Builder(); - } - - static ModalForm of(String title, String content, String button1, String button2) { - return ModalFormImpl.of(title, content, button1, button2); - } - - interface Builder extends FormBuilder { - Builder content(String content); - - Builder button1(String button1); - - Builder button2(String button2); - } -} diff --git a/common/src/main/java/org/geysermc/common/form/SimpleForm.java b/common/src/main/java/org/geysermc/common/form/SimpleForm.java deleted file mode 100644 index dd11544b9..000000000 --- a/common/src/main/java/org/geysermc/common/form/SimpleForm.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form; - -import org.geysermc.common.form.component.ButtonComponent; -import org.geysermc.common.form.impl.SimpleFormImpl; -import org.geysermc.common.form.response.SimpleFormResponse; - -import java.util.List; - -/** - * - */ -public interface SimpleForm extends Form { - static Builder builder() { - return new SimpleFormImpl.Builder(); - } - - static SimpleForm of(String title, String content, List buttons) { - return SimpleFormImpl.of(title, content, buttons); - } - - String getTitle(); - - String getContent(); - - List getButtons(); - - interface Builder extends FormBuilder { - Builder content(String content); - - Builder button(String text, FormImage.Type type, String data); - - Builder button(String text, FormImage image); - - Builder button(String text); - } -} diff --git a/common/src/main/java/org/geysermc/common/form/component/ButtonComponent.java b/common/src/main/java/org/geysermc/common/form/component/ButtonComponent.java deleted file mode 100644 index 3e8f5d438..000000000 --- a/common/src/main/java/org/geysermc/common/form/component/ButtonComponent.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.component; - -import org.geysermc.common.form.FormImage; -import org.geysermc.common.form.impl.component.ButtonComponentImpl; - -public interface ButtonComponent { - static ButtonComponent of(String text, FormImage image) { - return ButtonComponentImpl.of(text, image); - } - - static ButtonComponent of(String text, FormImage.Type type, String data) { - return ButtonComponentImpl.of(text, type, data); - } - - static ButtonComponent of(String text) { - return of(text, null); - } - - String getText(); - - FormImage getImage(); -} diff --git a/common/src/main/java/org/geysermc/common/form/component/Component.java b/common/src/main/java/org/geysermc/common/form/component/Component.java deleted file mode 100644 index 524d2a1a9..000000000 --- a/common/src/main/java/org/geysermc/common/form/component/Component.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.component; - -public interface Component { - ComponentType getType(); - - String getText(); -} diff --git a/common/src/main/java/org/geysermc/common/form/component/ComponentType.java b/common/src/main/java/org/geysermc/common/form/component/ComponentType.java deleted file mode 100644 index 898e10d07..000000000 --- a/common/src/main/java/org/geysermc/common/form/component/ComponentType.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.component; - -import com.google.gson.annotations.SerializedName; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@Getter -@RequiredArgsConstructor -public enum ComponentType { - @SerializedName("dropdown") - DROPDOWN(DropdownComponent.class), - @SerializedName("input") - INPUT(InputComponent.class), - @SerializedName("label") - LABEL(LabelComponent.class), - @SerializedName("slider") - SLIDER(SliderComponent.class), - @SerializedName("step_slider") - STEP_SLIDER(StepSliderComponent.class), - @SerializedName("toggle") - TOGGLE(ToggleComponent.class); - - private static final ComponentType[] VALUES = values(); - - private final String name = name().toLowerCase(); - private final Class componentClass; - - public static ComponentType getByName(String name) { - for (ComponentType type : VALUES) { - if (type.name.equals(name)) { - return type; - } - } - return null; - } -} diff --git a/common/src/main/java/org/geysermc/common/form/component/DropdownComponent.java b/common/src/main/java/org/geysermc/common/form/component/DropdownComponent.java deleted file mode 100644 index a68b3b1e2..000000000 --- a/common/src/main/java/org/geysermc/common/form/component/DropdownComponent.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.component; - -import org.geysermc.common.form.impl.component.DropdownComponentImpl; - -import java.util.List; -import java.util.function.Function; - -public interface DropdownComponent extends Component { - static DropdownComponent of(String text, List options, int defaultOption) { - return DropdownComponentImpl.of(text, options, defaultOption); - } - - static Builder builder() { - return new DropdownComponentImpl.Builder(); - } - - static Builder builder(String text) { - return builder().text(text); - } - - List getOptions(); - - int getDefaultOption(); - - interface Builder { - Builder text(String text); - - Builder option(String option, boolean isDefault); - - Builder option(String option); - - Builder defaultOption(int defaultOption); - - DropdownComponent build(); - - DropdownComponent translateAndBuild(Function translator); - } -} diff --git a/common/src/main/java/org/geysermc/common/form/component/InputComponent.java b/common/src/main/java/org/geysermc/common/form/component/InputComponent.java deleted file mode 100644 index 4f054957c..000000000 --- a/common/src/main/java/org/geysermc/common/form/component/InputComponent.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.component; - -import org.geysermc.common.form.impl.component.InputComponentImpl; - -public interface InputComponent extends Component { - static InputComponent of(String text, String placeholder, String defaultText) { - return InputComponentImpl.of(text, placeholder, defaultText); - } - - static InputComponent of(String text, String placeholder) { - return InputComponentImpl.of(text, placeholder, ""); - } - - static InputComponent of(String text) { - return InputComponentImpl.of(text, "", ""); - } - - String getPlaceholder(); - - String getDefaultText(); -} diff --git a/common/src/main/java/org/geysermc/common/form/component/LabelComponent.java b/common/src/main/java/org/geysermc/common/form/component/LabelComponent.java deleted file mode 100644 index ed6f47029..000000000 --- a/common/src/main/java/org/geysermc/common/form/component/LabelComponent.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.component; - -import org.geysermc.common.form.impl.component.LabelComponentImpl; - -public interface LabelComponent extends Component { - static LabelComponent of(String text) { - return LabelComponentImpl.of(text); - } -} diff --git a/common/src/main/java/org/geysermc/common/form/component/SliderComponent.java b/common/src/main/java/org/geysermc/common/form/component/SliderComponent.java deleted file mode 100644 index 9a0e3343e..000000000 --- a/common/src/main/java/org/geysermc/common/form/component/SliderComponent.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.component; - -import org.geysermc.common.form.impl.component.SliderComponentImpl; - -public interface SliderComponent extends Component { - static SliderComponent of(String text, float min, float max, int step, float defaultValue) { - return SliderComponentImpl.of(text, min, max, step, defaultValue); - } - - static SliderComponent of(String text, float min, float max, int step) { - return SliderComponentImpl.of(text, min, max, step); - } - - static SliderComponent of(String text, float min, float max, float defaultValue) { - return SliderComponentImpl.of(text, min, max, defaultValue); - } - - static SliderComponent of(String text, float min, float max) { - return SliderComponentImpl.of(text, min, max); - } - - float getMin(); - - float getMax(); - - int getStep(); - - float getDefaultValue(); -} diff --git a/common/src/main/java/org/geysermc/common/form/component/StepSliderComponent.java b/common/src/main/java/org/geysermc/common/form/component/StepSliderComponent.java deleted file mode 100644 index cda4e7acb..000000000 --- a/common/src/main/java/org/geysermc/common/form/component/StepSliderComponent.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.component; - -import org.geysermc.common.form.impl.component.StepSliderComponentImpl; - -import java.util.List; -import java.util.function.Function; - -public interface StepSliderComponent extends Component { - static StepSliderComponent of(String text, List steps, int defaultStep) { - return StepSliderComponentImpl.of(text, steps, defaultStep); - } - - static StepSliderComponent of(String text, int defaultStep, String... steps) { - return StepSliderComponentImpl.of(text, defaultStep, steps); - } - - static StepSliderComponent of(String text, String... steps) { - return StepSliderComponentImpl.of(text, steps); - } - - static Builder builder() { - return new StepSliderComponentImpl.Builder(); - } - - static Builder builder(String text) { - return builder().text(text); - } - - List getSteps(); - - int getDefaultStep(); - - interface Builder { - Builder text(String text); - - Builder step(String step, boolean defaultStep); - - Builder step(String step); - - Builder defaultStep(int defaultStep); - - StepSliderComponent build(); - - StepSliderComponent translateAndBuild(Function translator); - } -} diff --git a/common/src/main/java/org/geysermc/common/form/component/ToggleComponent.java b/common/src/main/java/org/geysermc/common/form/component/ToggleComponent.java deleted file mode 100644 index e505e995b..000000000 --- a/common/src/main/java/org/geysermc/common/form/component/ToggleComponent.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.component; - -import org.geysermc.common.form.impl.component.ToggleComponentImpl; - -public interface ToggleComponent extends Component { - static ToggleComponent of(String text, boolean defaultValue) { - return ToggleComponentImpl.of(text, defaultValue); - } - - static ToggleComponent of(String text) { - return ToggleComponentImpl.of(text); - } - - boolean getDefaultValue(); -} diff --git a/common/src/main/java/org/geysermc/common/form/impl/CustomFormImpl.java b/common/src/main/java/org/geysermc/common/form/impl/CustomFormImpl.java deleted file mode 100644 index 4d1f80713..000000000 --- a/common/src/main/java/org/geysermc/common/form/impl/CustomFormImpl.java +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.impl; - -import com.google.gson.annotations.JsonAdapter; -import lombok.Getter; -import org.geysermc.common.form.CustomForm; -import org.geysermc.common.form.FormImage; -import org.geysermc.common.form.FormType; -import org.geysermc.common.form.component.*; -import org.geysermc.common.form.impl.response.CustomFormResponseImpl; -import org.geysermc.common.form.impl.util.FormAdaptor; -import org.geysermc.common.form.impl.util.FormImageImpl; -import org.geysermc.common.form.response.CustomFormResponse; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -@Getter -@JsonAdapter(FormAdaptor.class) -public final class CustomFormImpl extends FormImpl implements CustomForm { - private final String title; - private final FormImage icon; - private final List content; - - private CustomFormImpl(String title, FormImage icon, List content) { - super(FormType.CUSTOM_FORM); - - this.title = title; - this.icon = icon; - this.content = Collections.unmodifiableList(content); - } - - public static CustomFormImpl of(String title, FormImage icon, List content) { - return new CustomFormImpl(title, icon, content); - } - - public CustomFormResponseImpl parseResponse(String data) { - if (isClosed(data)) { - return CustomFormResponseImpl.closed(); - } - return CustomFormResponseImpl.of(this, data); - } - - public static final class Builder extends FormImpl.Builder - implements CustomForm.Builder { - - private final List components = new ArrayList<>(); - private FormImage icon; - - public Builder icon(FormImage.Type type, String data) { - icon = FormImageImpl.of(type, data); - return this; - } - - public Builder iconPath(String path) { - return icon(FormImage.Type.PATH, path); - } - - public Builder iconUrl(String url) { - return icon(FormImage.Type.URL, url); - } - - public Builder component(Component component) { - components.add(component); - return this; - } - - public Builder dropdown(DropdownComponent.Builder dropdownBuilder) { - return component(dropdownBuilder.translateAndBuild(this::translate)); - } - - public Builder dropdown(String text, int defaultOption, String... options) { - List optionsList = new ArrayList<>(); - for (String option : options) { - optionsList.add(translate(option)); - } - return component(DropdownComponent.of(translate(text), optionsList, defaultOption)); - } - - public Builder dropdown(String text, String... options) { - return dropdown(text, -1, options); - } - - public Builder input(String text, String placeholder, String defaultText) { - return component(InputComponent.of( - translate(text), translate(placeholder), translate(defaultText) - )); - } - - public Builder input(String text, String placeholder) { - return component(InputComponent.of(translate(text), translate(placeholder))); - } - - public Builder input(String text) { - return component(InputComponent.of(translate(text))); - } - - public Builder label(String text) { - return component(LabelComponent.of(translate(text))); - } - - public Builder slider(String text, float min, float max, int step, float defaultValue) { - return component(SliderComponent.of(text, min, max, step, defaultValue)); - } - - public Builder slider(String text, float min, float max, int step) { - return slider(text, min, max, step, -1); - } - - public Builder slider(String text, float min, float max, float defaultValue) { - return slider(text, min, max, -1, defaultValue); - } - - public Builder slider(String text, float min, float max) { - return slider(text, min, max, -1, -1); - } - - public Builder stepSlider(StepSliderComponent.Builder stepSliderBuilder) { - return component(stepSliderBuilder.translateAndBuild(this::translate)); - } - - public Builder stepSlider(String text, int defaultStep, String... steps) { - List stepsList = new ArrayList<>(); - for (String option : steps) { - stepsList.add(translate(option)); - } - return component(StepSliderComponent.of(translate(text), stepsList, defaultStep)); - } - - public Builder stepSlider(String text, String... steps) { - return stepSlider(text, -1, steps); - } - - public Builder toggle(String text, boolean defaultValue) { - return component(ToggleComponent.of(translate(text), defaultValue)); - } - - public Builder toggle(String text) { - return component(ToggleComponent.of(translate(text))); - } - - @Override - public CustomFormImpl build() { - CustomFormImpl form = of(title, icon, components); - if (biResponseHandler != null) { - form.setResponseHandler(response -> biResponseHandler.accept(form, response)); - return form; - } - - form.setResponseHandler(responseHandler); - return form; - } - } -} diff --git a/common/src/main/java/org/geysermc/common/form/impl/FormImpl.java b/common/src/main/java/org/geysermc/common/form/impl/FormImpl.java deleted file mode 100644 index e9d648b8b..000000000 --- a/common/src/main/java/org/geysermc/common/form/impl/FormImpl.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.impl; - -import com.google.gson.Gson; -import lombok.Getter; -import lombok.Setter; -import org.geysermc.common.form.Form; -import org.geysermc.common.form.FormBuilder; -import org.geysermc.common.form.FormType; -import org.geysermc.common.form.Forms; -import org.geysermc.common.form.response.FormResponse; - -import java.util.function.BiConsumer; -import java.util.function.BiFunction; -import java.util.function.Consumer; - -@Getter -public abstract class FormImpl implements Form { - protected static final Gson GSON = Forms.getGson(); - - private final FormType type; - protected String hardcodedJsonData = null; - @Setter protected Consumer responseHandler; - - public FormImpl(FormType type) { - this.type = type; - } - - @Override - public String getJsonData() { - if (hardcodedJsonData != null) { - return hardcodedJsonData; - } - return GSON.toJson(this); - } - - @Override - public boolean isClosed(String response) { - return response == null || response.isEmpty() || response.equalsIgnoreCase("null"); - } - - public static abstract class Builder, F extends Form> - implements FormBuilder { - - protected String title = ""; - - protected BiFunction translationHandler = null; - protected BiConsumer biResponseHandler; - protected Consumer responseHandler; - protected String locale; - - @Override - public T title(String title) { - this.title = translate(title); - return self(); - } - - @Override - public T translator(BiFunction translator, String locale) { - this.translationHandler = translator; - this.locale = locale; - return title(title); - } - - @Override - public T translator(BiFunction translator) { - return translator(translator, locale); - } - - @Override - public T responseHandler(BiConsumer responseHandler) { - biResponseHandler = responseHandler; - return self(); - } - - @Override - public T responseHandler(Consumer responseHandler) { - this.responseHandler = responseHandler; - return self(); - } - - @Override - public abstract F build(); - - protected String translate(String text) { - if (translationHandler != null && text != null && !text.isEmpty()) { - return translationHandler.apply(text, locale); - } - return text; - } - - @SuppressWarnings("unchecked") - protected T self() { - return (T) this; - } - } -} diff --git a/common/src/main/java/org/geysermc/common/form/impl/ModalFormImpl.java b/common/src/main/java/org/geysermc/common/form/impl/ModalFormImpl.java deleted file mode 100644 index b047dc59d..000000000 --- a/common/src/main/java/org/geysermc/common/form/impl/ModalFormImpl.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.impl; - -import com.google.gson.annotations.JsonAdapter; -import lombok.Getter; -import org.geysermc.common.form.FormType; -import org.geysermc.common.form.ModalForm; -import org.geysermc.common.form.impl.response.ModalFormResponseImpl; -import org.geysermc.common.form.impl.util.FormAdaptor; -import org.geysermc.common.form.response.ModalFormResponse; - -@Getter -@JsonAdapter(FormAdaptor.class) -public final class ModalFormImpl extends FormImpl implements ModalForm { - private final String title; - private final String content; - private final String button1; - private final String button2; - - private ModalFormImpl(String title, String content, String button1, String button2) { - super(FormType.MODAL_FORM); - - this.title = title; - this.content = content; - this.button1 = button1; - this.button2 = button2; - } - - public static ModalFormImpl of(String title, String content, String button1, String button2) { - return new ModalFormImpl(title, content, button1, button2); - } - - public ModalFormResponse parseResponse(String data) { - if (isClosed(data)) { - return ModalFormResponseImpl.closed(); - } - data = data.trim(); - - if ("true".equals(data)) { - return ModalFormResponseImpl.of(0, button1); - } else if ("false".equals(data)) { - return ModalFormResponseImpl.of(1, button2); - } - return ModalFormResponseImpl.invalid(); - } - - public static final class Builder extends FormImpl.Builder - implements ModalForm.Builder { - - private String content = ""; - private String button1 = ""; - private String button2 = ""; - - public Builder content(String content) { - this.content = translate(content); - return this; - } - - public Builder button1(String button1) { - this.button1 = translate(button1); - return this; - } - - public Builder button2(String button2) { - this.button2 = translate(button2); - return this; - } - - @Override - public ModalForm build() { - ModalFormImpl form = of(title, content, button1, button2); - if (biResponseHandler != null) { - form.setResponseHandler(response -> biResponseHandler.accept(form, response)); - return form; - } - - form.setResponseHandler(responseHandler); - return form; - } - } -} diff --git a/common/src/main/java/org/geysermc/common/form/impl/SimpleFormImpl.java b/common/src/main/java/org/geysermc/common/form/impl/SimpleFormImpl.java deleted file mode 100644 index d5633ec05..000000000 --- a/common/src/main/java/org/geysermc/common/form/impl/SimpleFormImpl.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.impl; - -import com.google.gson.annotations.JsonAdapter; -import lombok.Getter; -import org.geysermc.common.form.FormImage; -import org.geysermc.common.form.FormType; -import org.geysermc.common.form.SimpleForm; -import org.geysermc.common.form.component.ButtonComponent; -import org.geysermc.common.form.impl.component.ButtonComponentImpl; -import org.geysermc.common.form.impl.response.SimpleFormResponseImpl; -import org.geysermc.common.form.impl.util.FormAdaptor; -import org.geysermc.common.form.response.SimpleFormResponse; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -@Getter -@JsonAdapter(FormAdaptor.class) -public final class SimpleFormImpl extends FormImpl implements SimpleForm { - private final String title; - private final String content; - private final List buttons; - - private SimpleFormImpl(String title, String content, List buttons) { - super(FormType.SIMPLE_FORM); - - this.title = title; - this.content = content; - this.buttons = Collections.unmodifiableList(buttons); - } - - public static SimpleFormImpl of(String title, String content, List buttons) { - return new SimpleFormImpl(title, content, buttons); - } - - public SimpleFormResponse parseResponse(String data) { - if (isClosed(data)) { - return SimpleFormResponseImpl.closed(); - } - data = data.trim(); - - int buttonId; - try { - buttonId = Integer.parseInt(data); - } catch (Exception exception) { - return SimpleFormResponseImpl.invalid(); - } - - if (buttonId >= buttons.size()) { - return SimpleFormResponseImpl.invalid(); - } - - return SimpleFormResponseImpl.of(buttonId, buttons.get(buttonId)); - } - - public static final class Builder extends FormImpl.Builder - implements SimpleForm.Builder { - - private final List buttons = new ArrayList<>(); - private String content = ""; - - public Builder content(String content) { - this.content = translate(content); - return this; - } - - public Builder button(String text, FormImage.Type type, String data) { - buttons.add(ButtonComponentImpl.of(translate(text), type, data)); - return this; - } - - public Builder button(String text, FormImage image) { - buttons.add(ButtonComponentImpl.of(translate(text), image)); - return this; - } - - public Builder button(String text) { - buttons.add(ButtonComponentImpl.of(translate(text))); - return this; - } - - @Override - public SimpleForm build() { - SimpleFormImpl form = of(title, content, buttons); - if (biResponseHandler != null) { - form.setResponseHandler(response -> biResponseHandler.accept(form, response)); - return form; - } - - form.setResponseHandler(responseHandler); - return form; - } - } -} diff --git a/common/src/main/java/org/geysermc/common/form/impl/component/ButtonComponentImpl.java b/common/src/main/java/org/geysermc/common/form/impl/component/ButtonComponentImpl.java deleted file mode 100644 index 0efac1a6d..000000000 --- a/common/src/main/java/org/geysermc/common/form/impl/component/ButtonComponentImpl.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.impl.component; - -import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import org.geysermc.common.form.FormImage; -import org.geysermc.common.form.component.ButtonComponent; -import org.geysermc.common.form.impl.util.FormImageImpl; - -@Getter -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) -public final class ButtonComponentImpl implements ButtonComponent { - private final String text; - private final FormImageImpl image; - - public static ButtonComponentImpl of(String text, FormImage image) { - return new ButtonComponentImpl(text, (FormImageImpl) image); - } - - public static ButtonComponentImpl of(String text, FormImage.Type type, String data) { - return of(text, FormImageImpl.of(type, data)); - } - - public static ButtonComponentImpl of(String text) { - return of(text, null); - } -} diff --git a/common/src/main/java/org/geysermc/common/form/impl/component/Component.java b/common/src/main/java/org/geysermc/common/form/impl/component/Component.java deleted file mode 100644 index ff2f29f87..000000000 --- a/common/src/main/java/org/geysermc/common/form/impl/component/Component.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.impl.component; - -import lombok.Getter; -import org.geysermc.common.form.component.ComponentType; - -import java.util.Objects; - -@Getter -public abstract class Component { - private final ComponentType type; - private final String text; - - Component(ComponentType type, String text) { - Objects.requireNonNull(type, "Type cannot be null"); - Objects.requireNonNull(text, "Text cannot be null"); - - this.type = type; - this.text = text; - } -} diff --git a/common/src/main/java/org/geysermc/common/form/impl/component/DropdownComponentImpl.java b/common/src/main/java/org/geysermc/common/form/impl/component/DropdownComponentImpl.java deleted file mode 100644 index ae175ab74..000000000 --- a/common/src/main/java/org/geysermc/common/form/impl/component/DropdownComponentImpl.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.impl.component; - -import com.google.gson.annotations.SerializedName; -import lombok.Getter; -import org.geysermc.common.form.component.ComponentType; -import org.geysermc.common.form.component.DropdownComponent; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.function.Function; - -@Getter -public final class DropdownComponentImpl extends Component implements DropdownComponent { - private final List options; - @SerializedName("default") - private final int defaultOption; - - private DropdownComponentImpl(String text, List options, int defaultOption) { - super(ComponentType.DROPDOWN, text); - this.options = Collections.unmodifiableList(options); - this.defaultOption = defaultOption; - } - - public static DropdownComponentImpl of(String text, List options, int defaultOption) { - if (defaultOption == -1 || defaultOption >= options.size()) { - defaultOption = 0; - } - return new DropdownComponentImpl(text, options, defaultOption); - } - - public static class Builder implements DropdownComponent.Builder { - private final List options = new ArrayList<>(); - private String text; - private int defaultOption = 0; - - @Override - public Builder text(String text) { - this.text = text; - return this; - } - - @Override - public Builder option(String option, boolean isDefault) { - options.add(option); - if (isDefault) { - defaultOption = options.size() - 1; - } - return this; - } - - @Override - public Builder option(String option) { - return option(option, false); - } - - @Override - public Builder defaultOption(int defaultOption) { - this.defaultOption = defaultOption; - return this; - } - - @Override - public DropdownComponentImpl build() { - return of(text, options, defaultOption); - } - - @Override - public DropdownComponentImpl translateAndBuild(Function translator) { - for (int i = 0; i < options.size(); i++) { - options.set(i, translator.apply(options.get(i))); - } - - return of(translator.apply(text), options, defaultOption); - } - } -} diff --git a/common/src/main/java/org/geysermc/common/form/impl/component/InputComponentImpl.java b/common/src/main/java/org/geysermc/common/form/impl/component/InputComponentImpl.java deleted file mode 100644 index ba741304b..000000000 --- a/common/src/main/java/org/geysermc/common/form/impl/component/InputComponentImpl.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.impl.component; - -import com.google.gson.annotations.SerializedName; -import lombok.Getter; -import org.geysermc.common.form.component.ComponentType; -import org.geysermc.common.form.component.InputComponent; - -@Getter -public final class InputComponentImpl extends Component implements InputComponent { - private final String placeholder; - @SerializedName("default") - private final String defaultText; - - private InputComponentImpl(String text, String placeholder, String defaultText) { - super(ComponentType.INPUT, text); - this.placeholder = placeholder; - this.defaultText = defaultText; - } - - public static InputComponentImpl of(String text, String placeholder, String defaultText) { - return new InputComponentImpl(text, placeholder, defaultText); - } - - public static InputComponentImpl of(String text, String placeholder) { - return new InputComponentImpl(text, placeholder, ""); - } - - public static InputComponentImpl of(String text) { - return new InputComponentImpl(text, "", ""); - } -} diff --git a/common/src/main/java/org/geysermc/common/form/impl/component/LabelComponentImpl.java b/common/src/main/java/org/geysermc/common/form/impl/component/LabelComponentImpl.java deleted file mode 100644 index 4dacb8406..000000000 --- a/common/src/main/java/org/geysermc/common/form/impl/component/LabelComponentImpl.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.impl.component; - -import lombok.Getter; -import org.geysermc.common.form.component.ComponentType; -import org.geysermc.common.form.component.LabelComponent; - -@Getter -public final class LabelComponentImpl extends Component implements LabelComponent { - private LabelComponentImpl(String text) { - super(ComponentType.LABEL, text); - } - - public static LabelComponentImpl of(String text) { - return new LabelComponentImpl(text); - } -} diff --git a/common/src/main/java/org/geysermc/common/form/impl/component/SliderComponentImpl.java b/common/src/main/java/org/geysermc/common/form/impl/component/SliderComponentImpl.java deleted file mode 100644 index 29fc18dc8..000000000 --- a/common/src/main/java/org/geysermc/common/form/impl/component/SliderComponentImpl.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.impl.component; - -import com.google.gson.annotations.SerializedName; -import lombok.Getter; -import org.geysermc.common.form.component.ComponentType; -import org.geysermc.common.form.component.SliderComponent; - -@Getter -public final class SliderComponentImpl extends Component implements SliderComponent { - private final float min; - private final float max; - private final int step; - @SerializedName("default") - private final float defaultValue; - - private SliderComponentImpl(String text, float min, float max, int step, float defaultValue) { - super(ComponentType.SLIDER, text); - this.min = min; - this.max = max; - this.step = step; - this.defaultValue = defaultValue; - } - - public static SliderComponentImpl of(String text, float min, float max, int step, - float defaultValue) { - min = Math.max(min, 0f); - max = Math.max(max, min); - - if (step < 1) { - step = 1; - } - - if (defaultValue == -1f) { - defaultValue = (int) Math.floor(min + max / 2D); - } - - return new SliderComponentImpl(text, min, max, step, defaultValue); - } - - public static SliderComponentImpl of(String text, float min, float max, int step) { - return of(text, min, max, step, -1); - } - - public static SliderComponentImpl of(String text, float min, float max, float defaultValue) { - return of(text, min, max, -1, defaultValue); - } - - public static SliderComponentImpl of(String text, float min, float max) { - return of(text, min, max, -1, -1); - } -} diff --git a/common/src/main/java/org/geysermc/common/form/impl/component/StepSliderComponentImpl.java b/common/src/main/java/org/geysermc/common/form/impl/component/StepSliderComponentImpl.java deleted file mode 100644 index dc87c54a5..000000000 --- a/common/src/main/java/org/geysermc/common/form/impl/component/StepSliderComponentImpl.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.impl.component; - -import com.google.gson.annotations.SerializedName; -import lombok.Getter; -import org.geysermc.common.form.component.ComponentType; -import org.geysermc.common.form.component.StepSliderComponent; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.function.Function; - -@Getter -public final class StepSliderComponentImpl extends Component implements StepSliderComponent { - private final List steps; - @SerializedName("default") - private final int defaultStep; - - private StepSliderComponentImpl(String text, List steps, int defaultStep) { - super(ComponentType.STEP_SLIDER, text); - this.steps = Collections.unmodifiableList(steps); - this.defaultStep = defaultStep; - } - - public static StepSliderComponentImpl of(String text, List steps, int defaultStep) { - if (text == null) { - text = ""; - } - - if (defaultStep >= steps.size() || defaultStep == -1) { - defaultStep = 0; - } - - return new StepSliderComponentImpl(text, steps, defaultStep); - } - - public static StepSliderComponentImpl of(String text, int defaultStep, String... steps) { - return of(text, Arrays.asList(steps), defaultStep); - } - - public static StepSliderComponentImpl of(String text, String... steps) { - return of(text, 0, steps); - } - - public static Builder builder() { - return new Builder(); - } - - public static Builder builder(String text) { - return builder().text(text); - } - - public static final class Builder implements StepSliderComponent.Builder { - private final List steps = new ArrayList<>(); - private String text; - private int defaultStep; - - public Builder text(String text) { - this.text = text; - return this; - } - - public Builder step(String step, boolean defaultStep) { - steps.add(step); - if (defaultStep) { - this.defaultStep = steps.size() - 1; - } - return this; - } - - public Builder step(String step) { - return step(step, false); - } - - public Builder defaultStep(int defaultStep) { - this.defaultStep = defaultStep; - return this; - } - - public StepSliderComponentImpl build() { - return of(text, steps, defaultStep); - } - - public StepSliderComponentImpl translateAndBuild(Function translator) { - for (int i = 0; i < steps.size(); i++) { - steps.set(i, translator.apply(steps.get(i))); - } - - return of(translator.apply(text), steps, defaultStep); - } - } -} diff --git a/common/src/main/java/org/geysermc/common/form/impl/component/ToggleComponentImpl.java b/common/src/main/java/org/geysermc/common/form/impl/component/ToggleComponentImpl.java deleted file mode 100644 index 0e29462b3..000000000 --- a/common/src/main/java/org/geysermc/common/form/impl/component/ToggleComponentImpl.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.impl.component; - -import com.google.gson.annotations.SerializedName; -import org.geysermc.common.form.component.ComponentType; -import org.geysermc.common.form.component.ToggleComponent; - -public final class ToggleComponentImpl extends Component implements ToggleComponent { - @SerializedName("default") - private final boolean defaultValue; - - private ToggleComponentImpl(String text, boolean defaultValue) { - super(ComponentType.TOGGLE, text); - this.defaultValue = defaultValue; - } - - public static ToggleComponentImpl of(String text, boolean defaultValue) { - return new ToggleComponentImpl(text, defaultValue); - } - - public static ToggleComponentImpl of(String text) { - return of(text, false); - } - - public boolean getDefaultValue() { - return defaultValue; - } -} diff --git a/common/src/main/java/org/geysermc/common/form/impl/response/CustomFormResponseImpl.java b/common/src/main/java/org/geysermc/common/form/impl/response/CustomFormResponseImpl.java deleted file mode 100644 index cc0ce64b7..000000000 --- a/common/src/main/java/org/geysermc/common/form/impl/response/CustomFormResponseImpl.java +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.impl.response; - -import com.google.gson.Gson; -import com.google.gson.JsonArray; -import com.google.gson.JsonPrimitive; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.ToString; -import org.geysermc.common.form.component.Component; -import org.geysermc.common.form.component.ComponentType; -import org.geysermc.common.form.impl.CustomFormImpl; -import org.geysermc.common.form.response.CustomFormResponse; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -@Getter -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) -@ToString -public final class CustomFormResponseImpl implements CustomFormResponse { - private static final Gson GSON = new Gson(); - private static final CustomFormResponseImpl CLOSED = - new CustomFormResponseImpl(true, false, null, null); - private static final CustomFormResponseImpl INVALID = - new CustomFormResponseImpl(false, true, null, null); - private final boolean closed; - private final boolean invalid; - - private final JsonArray responses; - private final List componentTypes; - - private int index = -1; - - public static CustomFormResponseImpl closed() { - return CLOSED; - } - - public static CustomFormResponseImpl invalid() { - return INVALID; - } - - public static CustomFormResponseImpl of(CustomFormImpl form, String responseData) { - JsonArray responses = GSON.fromJson(responseData, JsonArray.class); - List types = new ArrayList<>(); - for (Component component : form.getContent()) { - types.add(component.getType()); - } - return of(types, responses); - } - - public static CustomFormResponseImpl of(List componentTypes, - JsonArray responses) { - if (componentTypes.size() != responses.size()) { - return invalid(); - } - - return new CustomFormResponseImpl(false, false, responses, - Collections.unmodifiableList(componentTypes)); - } - - @Override - @SuppressWarnings("unchecked") - public T next(boolean includeLabels) { - if (!hasNext()) { - return null; - } - - while (++index < responses.size()) { - ComponentType type = componentTypes.get(index); - if (type == ComponentType.LABEL && !includeLabels) { - continue; - } - return (T) getDataFromType(type, index); - } - return null; // we don't have anything to check anymore - } - - @Override - public T next() { - return next(false); - } - - @Override - public void skip(int amount) { - index += amount; - } - - @Override - public void skip() { - skip(1); - } - - @Override - public void index(int index) { - this.index = index; - } - - @Override - public boolean hasNext() { - return responses.size() > index + 1; - } - - @Override - public JsonPrimitive get(int index) { - try { - return responses.get(index).getAsJsonPrimitive(); - } catch (IllegalStateException exception) { - wrongType(index, "a primitive"); - return null; - } - } - - @Override - public int getDropdown(int index) { - JsonPrimitive primitive = get(index); - if (!primitive.isNumber()) { - wrongType(index, "dropdown"); - } - return primitive.getAsInt(); - } - - @Override - public String getInput(int index) { - JsonPrimitive primitive = get(index); - if (!primitive.isString()) { - wrongType(index, "input"); - } - return primitive.getAsString(); - } - - @Override - public float getSlider(int index) { - JsonPrimitive primitive = get(index); - if (!primitive.isNumber()) { - wrongType(index, "slider"); - } - return primitive.getAsFloat(); - } - - @Override - public int getStepSlide(int index) { - JsonPrimitive primitive = get(index); - if (!primitive.isNumber()) { - wrongType(index, "step slider"); - } - return primitive.getAsInt(); - } - - @Override - public boolean getToggle(int index) { - JsonPrimitive primitive = get(index); - if (!primitive.isBoolean()) { - wrongType(index, "toggle"); - } - return primitive.getAsBoolean(); - } - - private Object getDataFromType(ComponentType type, int index) { - switch (type) { - case DROPDOWN: - return getDropdown(index); - case INPUT: - return getInput(index); - case SLIDER: - return getSlider(index); - case STEP_SLIDER: - return getStepSlide(index); - case TOGGLE: - return getToggle(index); - default: - return null; // label e.g. is always null - } - } - - private void wrongType(int index, String expected) { - throw new IllegalStateException(String.format( - "Expected %s on %s, got %s", - expected, index, responses.get(index).toString())); - } -} diff --git a/common/src/main/java/org/geysermc/common/form/impl/response/ModalFormResponseImpl.java b/common/src/main/java/org/geysermc/common/form/impl/response/ModalFormResponseImpl.java deleted file mode 100644 index 6d20431cd..000000000 --- a/common/src/main/java/org/geysermc/common/form/impl/response/ModalFormResponseImpl.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.impl.response; - -import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import org.geysermc.common.form.response.FormResponse; -import org.geysermc.common.form.response.ModalFormResponse; - -@Getter -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) -public final class ModalFormResponseImpl implements ModalFormResponse { - private static final ModalFormResponseImpl CLOSED = - new ModalFormResponseImpl(true, false, -1, null); - private static final ModalFormResponseImpl INVALID = - new ModalFormResponseImpl(false, true, -1, null); - private final boolean closed; - private final boolean invalid; - - private final int clickedButtonId; - private final String clickedButtonText; - - public static ModalFormResponseImpl closed() { - return CLOSED; - } - - public static ModalFormResponseImpl invalid() { - return INVALID; - } - - public static ModalFormResponseImpl of(int clickedButtonId, String clickedButtonText) { - return new ModalFormResponseImpl(false, false, clickedButtonId, clickedButtonText); - } - - public boolean getResult() { - return clickedButtonId == 0; - } -} diff --git a/common/src/main/java/org/geysermc/common/form/impl/response/SimpleFormResponseImpl.java b/common/src/main/java/org/geysermc/common/form/impl/response/SimpleFormResponseImpl.java deleted file mode 100644 index 24bc39366..000000000 --- a/common/src/main/java/org/geysermc/common/form/impl/response/SimpleFormResponseImpl.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.impl.response; - -import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import org.geysermc.common.form.component.ButtonComponent; -import org.geysermc.common.form.response.SimpleFormResponse; - -@Getter -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) -public final class SimpleFormResponseImpl implements SimpleFormResponse { - private static final SimpleFormResponseImpl CLOSED = - new SimpleFormResponseImpl(true, false, -1, null); - private static final SimpleFormResponseImpl INVALID = - new SimpleFormResponseImpl(false, true, -1, null); - private final boolean closed; - private final boolean invalid; - - private final int clickedButtonId; - private final ButtonComponent clickedButton; - - public static SimpleFormResponseImpl closed() { - return CLOSED; - } - - public static SimpleFormResponseImpl invalid() { - return INVALID; - } - - public static SimpleFormResponseImpl of(int clickedButtonId, ButtonComponent clickedButton) { - return new SimpleFormResponseImpl(false, false, clickedButtonId, clickedButton); - } - - public String getClickedButtonText() { - return clickedButton.getText(); - } -} diff --git a/common/src/main/java/org/geysermc/common/form/impl/util/FormAdaptor.java b/common/src/main/java/org/geysermc/common/form/impl/util/FormAdaptor.java deleted file mode 100644 index 11d7eefe4..000000000 --- a/common/src/main/java/org/geysermc/common/form/impl/util/FormAdaptor.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.impl.util; - -import com.google.gson.*; -import com.google.gson.reflect.TypeToken; -import org.geysermc.common.form.FormImage; -import org.geysermc.common.form.component.ButtonComponent; -import org.geysermc.common.form.component.Component; -import org.geysermc.common.form.component.ComponentType; -import org.geysermc.common.form.impl.CustomFormImpl; -import org.geysermc.common.form.impl.FormImpl; -import org.geysermc.common.form.impl.ModalFormImpl; -import org.geysermc.common.form.impl.SimpleFormImpl; -import org.geysermc.common.form.impl.component.ButtonComponentImpl; - -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.List; - -public final class FormAdaptor implements JsonDeserializer>, JsonSerializer> { - private static final Type LIST_BUTTON_TYPE = - new TypeToken>() {}.getType(); - - @Override - public FormImpl deserialize(JsonElement jsonElement, Type typeOfT, - JsonDeserializationContext context) - throws JsonParseException { - - if (!jsonElement.isJsonObject()) { - throw new JsonParseException("Form has to be a JsonObject"); - } - JsonObject json = jsonElement.getAsJsonObject(); - - if (typeOfT == SimpleFormImpl.class) { - String title = json.get("title").getAsString(); - String content = json.get("content").getAsString(); - List buttons = context - .deserialize(json.get("buttons"), LIST_BUTTON_TYPE); - return SimpleFormImpl.of(title, content, buttons); - } - - if (typeOfT == ModalFormImpl.class) { - String title = json.get("title").getAsString(); - String content = json.get("content").getAsString(); - String button1 = json.get("button1").getAsString(); - String button2 = json.get("button2").getAsString(); - return ModalFormImpl.of(title, content, button1, button2); - } - - if (typeOfT == CustomFormImpl.class) { - String title = json.get("title").getAsString(); - FormImage icon = context.deserialize(json.get("icon"), FormImageImpl.class); - List content = new ArrayList<>(); - - JsonArray contentArray = json.getAsJsonArray("content"); - for (JsonElement contentElement : contentArray) { - String typeName = contentElement.getAsJsonObject().get("type").getAsString(); - - ComponentType type = ComponentType.getByName(typeName); - if (type == null) { - throw new JsonParseException("Failed to find Component type " + typeName); - } - - content.add(context.deserialize(contentElement, type.getComponentClass())); - } - return CustomFormImpl.of(title, icon, content); - } - return null; - } - - @Override - public JsonElement serialize(FormImpl src, Type typeOfSrc, JsonSerializationContext context) { - JsonObject result = new JsonObject(); - result.add("type", context.serialize(src.getType())); - - if (typeOfSrc == SimpleFormImpl.class) { - SimpleFormImpl form = (SimpleFormImpl) src; - - result.addProperty("title", form.getTitle()); - result.addProperty("content", form.getContent()); - result.add("buttons", context.serialize(form.getButtons(), LIST_BUTTON_TYPE)); - return result; - } - - if (typeOfSrc == ModalFormImpl.class) { - ModalFormImpl form = (ModalFormImpl) src; - - result.addProperty("title", form.getTitle()); - result.addProperty("content", form.getContent()); - result.addProperty("button1", form.getButton1()); - result.addProperty("button2", form.getButton2()); - return result; - } - - if (typeOfSrc == CustomFormImpl.class) { - CustomFormImpl form = (CustomFormImpl) src; - - result.addProperty("title", form.getTitle()); - result.add("icon", context.serialize(form.getIcon())); - result.add("content", context.serialize(form.getContent())); - return result; - } - return null; - } -} diff --git a/common/src/main/java/org/geysermc/common/form/impl/util/FormImageImpl.java b/common/src/main/java/org/geysermc/common/form/impl/util/FormImageImpl.java deleted file mode 100644 index 4b8248e94..000000000 --- a/common/src/main/java/org/geysermc/common/form/impl/util/FormImageImpl.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.impl.util; - -import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import org.geysermc.common.form.FormImage; - -@Getter -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) -public final class FormImageImpl implements FormImage { - private final Type type; - private final String data; - - public static FormImageImpl of(Type type, String data) { - return new FormImageImpl(type, data); - } - - public static FormImageImpl of(String type, String data) { - return of(Type.getByName(type), data); - } -} diff --git a/common/src/main/java/org/geysermc/common/form/response/CustomFormResponse.java b/common/src/main/java/org/geysermc/common/form/response/CustomFormResponse.java deleted file mode 100644 index d4d0fd99e..000000000 --- a/common/src/main/java/org/geysermc/common/form/response/CustomFormResponse.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.response; - -import com.google.gson.JsonArray; -import com.google.gson.JsonPrimitive; -import org.geysermc.common.form.component.ComponentType; - -import java.util.List; - -public interface CustomFormResponse extends FormResponse { - JsonArray getResponses(); - - List getComponentTypes(); - - T next(boolean includeLabels); - - T next(); - - void skip(int amount); - - void skip(); - - void index(int index); - - boolean hasNext(); - - JsonPrimitive get(int index); - - int getDropdown(int index); - - String getInput(int index); - - float getSlider(int index); - - int getStepSlide(int index); - - boolean getToggle(int index); -} diff --git a/common/src/main/java/org/geysermc/common/form/response/FormResponse.java b/common/src/main/java/org/geysermc/common/form/response/FormResponse.java deleted file mode 100644 index 7c62236f6..000000000 --- a/common/src/main/java/org/geysermc/common/form/response/FormResponse.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.response; - -public interface FormResponse { - boolean isClosed(); - boolean isInvalid(); - - default boolean isCorrect() { - return !isClosed() && !isInvalid(); - } -} diff --git a/common/src/main/java/org/geysermc/common/form/response/ModalFormResponse.java b/common/src/main/java/org/geysermc/common/form/response/ModalFormResponse.java deleted file mode 100644 index 6e594e0ee..000000000 --- a/common/src/main/java/org/geysermc/common/form/response/ModalFormResponse.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.response; - -public interface ModalFormResponse extends FormResponse { - int getClickedButtonId(); - - String getClickedButtonText(); - - boolean getResult(); -} diff --git a/common/src/main/java/org/geysermc/common/form/response/SimpleFormResponse.java b/common/src/main/java/org/geysermc/common/form/response/SimpleFormResponse.java deleted file mode 100644 index 528a79ed1..000000000 --- a/common/src/main/java/org/geysermc/common/form/response/SimpleFormResponse.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.response; - -import org.geysermc.common.form.component.ButtonComponent; - -public interface SimpleFormResponse extends FormResponse { - int getClickedButtonId(); - - ButtonComponent getClickedButton(); -} diff --git a/connector/pom.xml b/connector/pom.xml index 7c44ddfd2..3fb7634c0 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -15,7 +15,6 @@ org.geysermc common 1.2.0-SNAPSHOT - compile com.fasterxml.jackson.dataformat 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 5f0a844b0..c461b1d07 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 @@ -57,8 +57,6 @@ import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap; import lombok.Getter; import lombok.NonNull; import lombok.Setter; -import org.geysermc.common.form.Form; -import org.geysermc.common.form.FormBuilder; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.CommandSender; import org.geysermc.connector.common.AuthType; @@ -76,6 +74,8 @@ import org.geysermc.connector.network.translators.chat.MessageTranslator; import org.geysermc.connector.network.translators.inventory.EnchantmentInventoryTranslator; import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.utils.*; +import org.geysermc.cumulus.Form; +import org.geysermc.cumulus.util.FormBuilder; import org.geysermc.floodgate.crypto.FloodgateCipher; import org.geysermc.floodgate.util.BedrockData; @@ -607,7 +607,7 @@ public class GeyserSession implements CommandSender { return this.upstream.getAddress(); } - public void sendForm(Form form) { + public void sendForm(Form form) { formCache.showForm(form); } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java index 1f112e9ce..26589a82e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java @@ -31,8 +31,8 @@ import com.nukkitx.protocol.bedrock.packet.NetworkStackLatencyPacket; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import lombok.RequiredArgsConstructor; -import org.geysermc.common.form.Form; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.cumulus.Form; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -41,16 +41,16 @@ import java.util.function.Consumer; @RequiredArgsConstructor public class FormCache { private final AtomicInteger formId = new AtomicInteger(0); - private final Int2ObjectMap> forms = new Int2ObjectOpenHashMap<>(); + private final Int2ObjectMap forms = new Int2ObjectOpenHashMap<>(); private final GeyserSession session; - public int addForm(Form form) { + public int addForm(Form form) { int windowId = formId.getAndIncrement(); forms.put(windowId, form); return windowId; } - public int showForm(Form form) { + public int showForm(Form form) { int windowId = addForm(form); ModalFormRequestPacket formRequestPacket = new ModalFormRequestPacket(); @@ -62,15 +62,15 @@ public class FormCache { NetworkStackLatencyPacket latencyPacket = new NetworkStackLatencyPacket(); latencyPacket.setFromServer(true); latencyPacket.setTimestamp(-System.currentTimeMillis()); - session.getConnector().getGeneralThreadPool().schedule(() -> - session.sendUpstreamPacket(latencyPacket), + session.getConnector().getGeneralThreadPool().schedule( + () -> session.sendUpstreamPacket(latencyPacket), 500, TimeUnit.MILLISECONDS); return windowId; } public void handleResponse(ModalFormResponsePacket response) { - Form form = forms.get(response.getFormId()); + Form form = forms.get(response.getFormId()); if (form == null) { return; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockServerSettingsRequestTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockServerSettingsRequestTranslator.java index 01bf07b9c..99dde0f6c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockServerSettingsRequestTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockServerSettingsRequestTranslator.java @@ -27,15 +27,14 @@ package org.geysermc.connector.network.translators.bedrock; import com.nukkitx.protocol.bedrock.packet.ServerSettingsRequestPacket; import com.nukkitx.protocol.bedrock.packet.ServerSettingsResponsePacket; -import org.geysermc.common.form.CustomForm; 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.SettingsUtils; +import org.geysermc.cumulus.CustomForm; @Translator(packet = ServerSettingsRequestPacket.class) public class BedrockServerSettingsRequestTranslator extends PacketTranslator { - @Override public void translate(ServerSettingsRequestPacket packet, GeyserSession session) { CustomForm window = SettingsUtils.buildForm(session); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaPluginMessageTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaPluginMessageTranslator.java index 57d64ac75..337dc0b74 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaPluginMessageTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaPluginMessageTranslator.java @@ -28,13 +28,13 @@ package org.geysermc.connector.network.translators.java; import com.github.steveice10.mc.protocol.packet.ingame.client.ClientPluginMessagePacket; import com.github.steveice10.mc.protocol.packet.ingame.server.ServerPluginMessagePacket; import com.google.common.base.Charsets; -import org.geysermc.common.form.Form; -import org.geysermc.common.form.FormType; -import org.geysermc.common.form.Forms; import org.geysermc.connector.common.AuthType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; +import org.geysermc.cumulus.Form; +import org.geysermc.cumulus.Forms; +import org.geysermc.cumulus.util.FormType; import java.nio.charset.StandardCharsets; @@ -63,7 +63,7 @@ public class JavaPluginMessageTranslator extends PacketTranslator form = Forms.fromJson(dataString, type.getTypeClass()); + Form form = Forms.fromJson(dataString, type); form.setResponseHandler(response -> { byte[] raw = response.getBytes(StandardCharsets.UTF_8); byte[] finalData = new byte[raw.length + 2]; 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 84997deb6..af41a70ec 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java @@ -34,14 +34,14 @@ import com.nukkitx.network.util.Preconditions; import com.nukkitx.protocol.bedrock.packet.LoginPacket; import com.nukkitx.protocol.bedrock.packet.ServerToClientHandshakePacket; import com.nukkitx.protocol.bedrock.util.EncryptionUtils; -import org.geysermc.common.form.CustomForm; -import org.geysermc.common.form.SimpleForm; -import org.geysermc.common.form.response.CustomFormResponse; -import org.geysermc.common.form.response.SimpleFormResponse; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.auth.AuthData; import org.geysermc.connector.network.session.auth.BedrockClientData; +import org.geysermc.cumulus.CustomForm; +import org.geysermc.cumulus.SimpleForm; +import org.geysermc.cumulus.response.CustomFormResponse; +import org.geysermc.cumulus.response.SimpleFormResponse; import javax.crypto.SecretKey; import java.io.IOException; diff --git a/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java b/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java index d3d1c703b..e4d762a57 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java @@ -27,12 +27,12 @@ package org.geysermc.connector.utils; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import com.github.steveice10.mc.protocol.data.game.setting.Difficulty; -import org.geysermc.common.form.CustomForm; -import org.geysermc.common.form.component.DropdownComponent; -import org.geysermc.common.form.response.CustomFormResponse; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.WorldManager; +import org.geysermc.cumulus.CustomForm; +import org.geysermc.cumulus.component.DropdownComponent; +import org.geysermc.cumulus.response.CustomFormResponse; public class SettingsUtils { diff --git a/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java b/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java index 41084504a..ea0034c96 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java @@ -28,11 +28,11 @@ package org.geysermc.connector.utils; import com.github.steveice10.mc.protocol.data.MagicValues; import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType; import com.github.steveice10.mc.protocol.data.game.statistic.*; -import org.geysermc.common.form.SimpleForm; -import org.geysermc.common.form.response.SimpleFormResponse; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.network.translators.world.block.BlockTranslator; +import org.geysermc.cumulus.SimpleForm; +import org.geysermc.cumulus.response.SimpleFormResponse; import java.util.Map; import java.util.regex.Matcher; From 9500da2ef0901a7288b63e229d8bafd716ff389d Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sat, 12 Dec 2020 01:39:09 +0100 Subject: [PATCH 017/107] Small changes --- .../connector/network/session/cache/FormCache.java | 2 +- .../connector/utils/LoginEncryptionUtils.java | 11 ++++------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java index 26589a82e..cdd8a01ac 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java @@ -77,7 +77,7 @@ public class FormCache { Consumer responseConsumer = form.getResponseHandler(); if (responseConsumer != null) { - responseConsumer.accept(response.getFormData().trim()); + responseConsumer.accept(response.getFormData()); } removeWindow(response.getFormId()); 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 af41a70ec..39e9e1738 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java @@ -154,17 +154,15 @@ public class LoginEncryptionUtils { } public static void buildAndShowLoginWindow(GeyserSession session) { - String userLanguage = session.getClientData().getLanguageCode(); - session.sendForm( SimpleForm.builder() - .translator(LanguageUtils::getPlayerLocaleString, userLanguage) + .translator(LanguageUtils::getPlayerLocaleString, session.getLocale()) .title("geyser.auth.login.form.notice.title") .content("geyser.auth.login.form.notice.desc") .button("geyser.auth.login.form.notice.btn_login") // id = 0 .button("geyser.auth.login.form.notice.btn_disconnect") .responseHandler((form, responseData) -> { - SimpleFormResponse response = form.parseResponse(responseData.trim()); + SimpleFormResponse response = form.parseResponse(responseData); if (!response.isCorrect()) { buildAndShowLoginWindow(session); return; @@ -180,16 +178,15 @@ public class LoginEncryptionUtils { } public static void buildAndShowLoginDetailsWindow(GeyserSession session) { - String userLanguage = session.getLocale(); session.sendForm( CustomForm.builder() - .translator(LanguageUtils::getPlayerLocaleString, userLanguage) + .translator(LanguageUtils::getPlayerLocaleString, session.getLocale()) .title("geyser.auth.login.form.details.title") .label("geyser.auth.login.form.details.desc") .input("geyser.auth.login.form.details.email", "account@geysermc.org", "") .input("geyser.auth.login.form.details.pass", "123456", "") .responseHandler((form, responseData) -> { - CustomFormResponse response = form.parseResponse(responseData.trim()); + CustomFormResponse response = form.parseResponse(responseData); if (!response.isCorrect()) { buildAndShowLoginDetailsWindow(session); return; From e69ad0e560073651ca38bcb267b01f4762f9e0bd Mon Sep 17 00:00:00 2001 From: Tim203 Date: Tue, 15 Dec 2020 22:31:14 +0100 Subject: [PATCH 018/107] Geyser can compile now --- .../connector/command/defaults/SettingsCommand.java | 6 +++--- .../connector/network/session/auth/BedrockClientData.java | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/SettingsCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/SettingsCommand.java index 8f09c7747..2b72b77c7 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/SettingsCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/SettingsCommand.java @@ -56,9 +56,9 @@ public class SettingsCommand extends GeyserCommand { } } } - if (session == null) return; - SettingsUtils.buildForm(session); - session.sendForm(session.getSettingsForm(), SettingsUtils.SETTINGS_FORM_ID); + if (session != null) { + session.sendForm(SettingsUtils.buildForm(session)); + } } @Override diff --git a/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java b/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java index 905245ee4..f864d022d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java @@ -31,7 +31,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.JsonNode; import com.google.common.base.Charsets; import lombok.Getter; -import org.geysermc.connector.utils.SkinProvider; +import org.geysermc.connector.skin.SkinProvider; import org.geysermc.floodgate.util.DeviceOs; import org.geysermc.floodgate.util.InputMode; import org.geysermc.floodgate.util.RawSkin; From 2f1acb1e6f9429bc9c09ad69bb862cf59235648e Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sat, 19 Dec 2020 22:45:34 +0100 Subject: [PATCH 019/107] Separate method for Base64 length calc. Added offset method in RawSkin --- .../geysermc/floodgate/util/Base64Utils.java | 35 +++++++++++++++++++ .../org/geysermc/floodgate/util/RawSkin.java | 11 ++++-- 2 files changed, 43 insertions(+), 3 deletions(-) create mode 100644 common/src/main/java/org/geysermc/floodgate/util/Base64Utils.java diff --git a/common/src/main/java/org/geysermc/floodgate/util/Base64Utils.java b/common/src/main/java/org/geysermc/floodgate/util/Base64Utils.java new file mode 100644 index 000000000..388b54838 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/util/Base64Utils.java @@ -0,0 +1,35 @@ +/* + * 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.floodgate.util; + +public class Base64Utils { + public static int getEncodedLength(int length) { + if (length <= 0) { + return -1; + } + return 4 * ((length + 2) / 3); + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/util/RawSkin.java b/common/src/main/java/org/geysermc/floodgate/util/RawSkin.java index 152cbec5a..6759ffa3c 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/RawSkin.java +++ b/common/src/main/java/org/geysermc/floodgate/util/RawSkin.java @@ -45,13 +45,18 @@ public final class RawSkin { } public static RawSkin decode(byte[] data) throws InvalidFormatException { + return decode(data, 0); + } + + public static RawSkin decode(byte[] data, int offset) throws InvalidFormatException { + // offset is an amount of bytes before the Base64 starts if (data == null) { return null; } - int maxEncodedLength = 4 * (((64 * 64 * 4 + 9) + 2) / 3); + int maxEncodedLength = Base64Utils.getEncodedLength(64 * 64 * 4 + 9); // if the RawSkin is longer then the max Java Edition skin length - if (data.length > maxEncodedLength) { + if ((data.length - offset) > maxEncodedLength) { throw new InvalidFormatException(format( "Encoded data cannot be longer then %s bytes! Got %s", maxEncodedLength, data.length @@ -59,7 +64,7 @@ public final class RawSkin { } // if the encoded data doesn't even contain the width, height (8 bytes, 2 ints) and isAlex - if (data.length < 4 * ((9 + 2) / 3)) { + if ((data.length - offset) < Base64Utils.getEncodedLength(9)) { throw new InvalidFormatException("Encoded data must be at least 16 bytes long!"); } From d6c2507bb5480d29ab8ca37358f96bd22a7f74df Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sun, 20 Dec 2020 17:34:46 +0100 Subject: [PATCH 020/107] Fixed some bugs --- .../geysermc/floodgate/util/BedrockData.java | 5 +++-- .../org/geysermc/floodgate/util/RawSkin.java | 21 ++++++++++++------- .../connector/utils/PluginMessageUtils.java | 7 +------ 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java index 08c1e28d3..cc8f9eeea 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java +++ b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java @@ -76,7 +76,7 @@ public final class BedrockData { return new BedrockData( split[0], split[1], split[2], Integer.parseInt(split[3]), split[4], Integer.parseInt(split[5]), Integer.parseInt(split[6]), split[7], - linkedPlayer, Boolean.parseBoolean(split[9]), split.length + linkedPlayer, "1".equals(split[8]), split.length ); } @@ -93,6 +93,7 @@ public final class BedrockData { // The format is the same as the order of the fields in this class return version + '\0' + username + '\0' + xuid + '\0' + deviceOs + '\0' + languageCode + '\0' + uiProfile + '\0' + inputMode + '\0' + ip + '\0' + - fromProxy + '\0' + (linkedPlayer != null ? linkedPlayer.toString() : "null"); + (fromProxy ? 1 : 0) + '\0' + + (linkedPlayer != null ? linkedPlayer.toString() : "null"); } } diff --git a/common/src/main/java/org/geysermc/floodgate/util/RawSkin.java b/common/src/main/java/org/geysermc/floodgate/util/RawSkin.java index 6759ffa3c..470b7e24a 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/RawSkin.java +++ b/common/src/main/java/org/geysermc/floodgate/util/RawSkin.java @@ -26,7 +26,6 @@ package org.geysermc.floodgate.util; import lombok.AllArgsConstructor; -import lombok.ToString; import java.nio.ByteBuffer; import java.util.Base64; @@ -34,7 +33,6 @@ import java.util.Base64; import static java.lang.String.format; @AllArgsConstructor -@ToString public final class RawSkin { public int width; public int height; @@ -44,11 +42,20 @@ public final class RawSkin { private RawSkin() { } - public static RawSkin decode(byte[] data) throws InvalidFormatException { - return decode(data, 0); + public static RawSkin decode(byte[] data, int offset) throws InvalidFormatException { + if (data == null || offset < 0 || data.length <= offset) { + return null; + } + if (offset == 0) { + return decode(data); + } + + byte[] rawSkin = new byte[data.length - offset]; + System.arraycopy(data, offset, rawSkin, 0, rawSkin.length); + return decode(rawSkin); } - public static RawSkin decode(byte[] data, int offset) throws InvalidFormatException { + public static RawSkin decode(byte[] data) throws InvalidFormatException { // offset is an amount of bytes before the Base64 starts if (data == null) { return null; @@ -56,7 +63,7 @@ public final class RawSkin { int maxEncodedLength = Base64Utils.getEncodedLength(64 * 64 * 4 + 9); // if the RawSkin is longer then the max Java Edition skin length - if ((data.length - offset) > maxEncodedLength) { + if (data.length > maxEncodedLength) { throw new InvalidFormatException(format( "Encoded data cannot be longer then %s bytes! Got %s", maxEncodedLength, data.length @@ -64,7 +71,7 @@ public final class RawSkin { } // if the encoded data doesn't even contain the width, height (8 bytes, 2 ints) and isAlex - if ((data.length - offset) < Base64Utils.getEncodedLength(9)) { + if (data.length < Base64Utils.getEncodedLength(9)) { throw new InvalidFormatException("Encoded data must be at least 16 bytes long!"); } diff --git a/connector/src/main/java/org/geysermc/connector/utils/PluginMessageUtils.java b/connector/src/main/java/org/geysermc/connector/utils/PluginMessageUtils.java index 4c7547d3a..475380ab3 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/PluginMessageUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/PluginMessageUtils.java @@ -42,12 +42,7 @@ public class PluginMessageUtils { .put(data) .array(); - data = "floodgate:skin\0floodgate:form".getBytes(Charsets.UTF_8); - FLOODGATE_REGISTER_DATA = - ByteBuffer.allocate(data.length + getVarIntLength(data.length)) - .put(writeVarInt(data.length)) - .put(data) - .array(); + FLOODGATE_REGISTER_DATA = "floodgate:skin\0floodgate:form".getBytes(Charsets.UTF_8); } /** From 9fbf7047a1db4845e48965c9752ef4ce6ee1b451 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Thu, 31 Dec 2020 17:18:39 +0100 Subject: [PATCH 021/107] Fixed a NoSuchMethodError --- .../main/java/org/geysermc/floodgate/crypto/AesCipher.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/common/src/main/java/org/geysermc/floodgate/crypto/AesCipher.java b/common/src/main/java/org/geysermc/floodgate/crypto/AesCipher.java index 2627584f6..f602f4be0 100644 --- a/common/src/main/java/org/geysermc/floodgate/crypto/AesCipher.java +++ b/common/src/main/java/org/geysermc/floodgate/crypto/AesCipher.java @@ -31,6 +31,7 @@ import lombok.RequiredArgsConstructor; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.spec.GCMParameterSpec; +import java.nio.Buffer; import java.nio.ByteBuffer; import java.security.Key; import java.security.SecureRandom; @@ -99,13 +100,15 @@ public final class AesCipher implements FloodgateCipher { } ivLength = buffer.position() - mark - 1; // don't include the splitter itself - buffer.position(mark); // reset to the pre-while index + // don't remove this cast, it'll cause problems if you remove it + ((Buffer) buffer).position(mark); // reset to the pre-while index } byte[] iv = new byte[ivLength]; buffer.get(iv); - buffer.position(buffer.position() + 1); // skip splitter + // don't remove this cast, it'll cause problems if you remove it + ((Buffer) buffer).position(buffer.position() + 1); // skip splitter byte[] cipherText = new byte[buffer.remaining()]; buffer.get(cipherText); From ce2734d3b97aa36728a32e7517e8a9324f167fec Mon Sep 17 00:00:00 2001 From: Tim203 Date: Tue, 12 Jan 2021 20:55:11 +0100 Subject: [PATCH 022/107] Allow BedrockData and LinkedPlayer cloning --- .../main/java/org/geysermc/floodgate/util/BedrockData.java | 7 ++++++- .../java/org/geysermc/floodgate/util/LinkedPlayer.java | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java index 9064b3f36..5f449fe2d 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java +++ b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java @@ -36,7 +36,7 @@ import lombok.Getter; */ @Getter @AllArgsConstructor(access = AccessLevel.PRIVATE) -public final class BedrockData { +public final class BedrockData implements Cloneable { public static final int EXPECTED_LENGTH = 10; private final String version; @@ -96,4 +96,9 @@ public final class BedrockData { (fromProxy ? 1 : 0) + '\0' + (linkedPlayer != null ? linkedPlayer.toString() : "null"); } + + @Override + public BedrockData clone() throws CloneNotSupportedException { + return (BedrockData) super.clone(); + } } diff --git a/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java b/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java index 1e8d67c27..950f0eb55 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java +++ b/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java @@ -34,7 +34,7 @@ import java.util.UUID; @Getter @RequiredArgsConstructor(access = AccessLevel.PRIVATE) -public final class LinkedPlayer { +public final class LinkedPlayer implements Cloneable { /** * The Java username of the linked player */ @@ -74,4 +74,9 @@ public final class LinkedPlayer { public String toString() { return javaUsername + ';' + javaUniqueId.toString() + ';' + bedrockId.toString(); } + + @Override + public LinkedPlayer clone() throws CloneNotSupportedException { + return (LinkedPlayer) super.clone(); + } } From ad7ffabb6d6f74190d7ddbcb0bf81a5ca726e072 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sun, 24 Jan 2021 01:29:47 +0100 Subject: [PATCH 023/107] Make the identifier more unique --- .../floodgate/crypto/FloodgateCipher.java | 47 +++++++++---------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/common/src/main/java/org/geysermc/floodgate/crypto/FloodgateCipher.java b/common/src/main/java/org/geysermc/floodgate/crypto/FloodgateCipher.java index 4869531e2..69c8f9f6d 100644 --- a/common/src/main/java/org/geysermc/floodgate/crypto/FloodgateCipher.java +++ b/common/src/main/java/org/geysermc/floodgate/crypto/FloodgateCipher.java @@ -37,9 +37,24 @@ import java.security.Key; * Responsible for both encrypting and decrypting data */ public interface FloodgateCipher { - byte[] IDENTIFIER = "Floodgate".getBytes(StandardCharsets.UTF_8); + // use invalid username characters at the beginning and the end of the identifier, + // to make sure that it doesn't get messed up with usernames + byte[] IDENTIFIER = "^Floodgate^".getBytes(StandardCharsets.UTF_8); int HEADER_LENGTH = IDENTIFIER.length; + static boolean hasHeader(String data) { + if (data.length() < IDENTIFIER.length) { + return false; + } + + for (int i = 0; i < IDENTIFIER.length; i++) { + if (IDENTIFIER[i] != data.charAt(i)) { + return false; + } + } + return true; + } + /** * Initializes the instance by giving it the key it needs to encrypt or decrypt data * @@ -57,8 +72,7 @@ public interface FloodgateCipher { byte[] encrypt(byte[] data) throws Exception; /** - * Encrypts data from a String.
- * This method internally calls {@link #encrypt(byte[])} + * Encrypts data from a String.
This method internally calls {@link #encrypt(byte[])} * * @param data the data to encrypt * @return the encrypted data @@ -78,9 +92,8 @@ public interface FloodgateCipher { byte[] decrypt(byte[] data) throws Exception; /** - * Decrypts a byte[] and turn it into a String.
- * This method internally calls {@link #decrypt(byte[])} - * and converts the returned byte[] into a String. + * Decrypts a byte[] and turn it into a String.
This method internally calls {@link + * #decrypt(byte[])} and converts the returned byte[] into a String. * * @param data the data to encrypt * @return the decrypted data in a UTF-8 String @@ -95,9 +108,8 @@ public interface FloodgateCipher { } /** - * Decrypts a String.
- * This method internally calls {@link #decrypt(byte[])} - * by converting the UTF-8 String into a byte[] + * Decrypts a String.
This method internally calls {@link #decrypt(byte[])} by converting + * the UTF-8 String into a byte[] * * @param data the data to decrypt * @return the decrypted data in a byte[] @@ -108,8 +120,8 @@ public interface FloodgateCipher { } /** - * Checks if the header is valid. - * This method will throw an InvalidFormatException when the header is invalid. + * Checks if the header is valid. This method will throw an InvalidFormatException when the + * header is invalid. * * @param data the data to check * @throws InvalidFormatException when the header is invalid @@ -142,19 +154,6 @@ public interface FloodgateCipher { } } - static boolean hasHeader(String data) { - if (data.length() < IDENTIFIER.length) { - return false; - } - - for (int i = 0; i < IDENTIFIER.length; i++) { - if (IDENTIFIER[i] != data.charAt(i)) { - return false; - } - } - return true; - } - @Data @AllArgsConstructor class HeaderResult { From cf149b58e07d80e49dd7b9b8cd974e0d27304cad Mon Sep 17 00:00:00 2001 From: Tim203 Date: Tue, 2 Feb 2021 00:46:46 +0100 Subject: [PATCH 024/107] Fixed remaining merge conflicts --- .../connector/command/CommandManager.java | 2 +- .../command/defaults/AdvancementsCommand.java | 13 +- .../network/session/GeyserSession.java | 4 +- .../session/cache/AdvancementsCache.java | 261 ++++++++---------- .../java/JavaAdvancementsTabTranslator.java | 7 +- .../connector/utils/LoginEncryptionUtils.java | 7 +- 6 files changed, 120 insertions(+), 174 deletions(-) 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 9f675ae81..71eb2c742 100644 --- a/connector/src/main/java/org/geysermc/connector/command/CommandManager.java +++ b/connector/src/main/java/org/geysermc/connector/command/CommandManager.java @@ -53,7 +53,7 @@ public abstract class CommandManager { registerCommand(new VersionCommand(connector, "version", "geyser.commands.version.desc", "geyser.command.version")); registerCommand(new SettingsCommand(connector, "settings", "geyser.commands.settings.desc", "geyser.command.settings")); registerCommand(new StatisticsCommand(connector, "statistics", "geyser.commands.statistics.desc", "geyser.command.statistics")); - registerCommand(new AdvancementsCommand(connector, "advancements", "geyser.commands.advancements.desc", "geyser.command.advancements")); + registerCommand(new AdvancementsCommand( "advancements", "geyser.commands.advancements.desc", "geyser.command.advancements")); } public void registerCommand(GeyserCommand command) { diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/AdvancementsCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/AdvancementsCommand.java index 2ef23381b..8ace83840 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/AdvancementsCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/AdvancementsCommand.java @@ -25,25 +25,20 @@ package org.geysermc.connector.command.defaults; -import org.geysermc.common.window.SimpleFormWindow; -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.network.session.cache.AdvancementsCache; public class AdvancementsCommand extends GeyserCommand { - - public AdvancementsCommand(GeyserConnector connector, String name, String description, String permission) { + public AdvancementsCommand(String name, String description, String permission) { super(name, description, permission); } @Override public void execute(GeyserSession session, CommandSender sender, String[] args) { - if (session == null) return; - - SimpleFormWindow window = session.getAdvancementsCache().buildMenuForm(); - session.sendForm(window, AdvancementsCache.ADVANCEMENTS_MENU_FORM_ID); + if (session != null) { + session.getAdvancementsCache().buildAndShowMenuForm(); + } } @Override 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 acfd8ae21..8bdb7f2b1 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 @@ -489,7 +489,7 @@ public class GeyserSession implements CommandSender { MsaAuthenticationService msaAuthenticationService = new MsaAuthenticationService(GeyserConnector.OAUTH_CLIENT_ID); MsaAuthenticationService.MsCodeResponse response = msaAuthenticationService.getAuthCode(); - LoginEncryptionUtils.showMicrosoftCodeWindow(this, response); + LoginEncryptionUtils.buildAndShowMicrosoftCodeWindow(this, response); // This just looks cool SetTimePacket packet = new SetTimePacket(); @@ -605,7 +605,7 @@ public class GeyserSession implements CommandSender { // 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")) { + if (locale.equalsIgnoreCase("en_us") && !LocaleUtils.LOCALE_MAPPINGS.containsKey("en_us")) { // This should probably be left hardcoded as it will only show for en_us clients sendMessage("Loading your locale (en_us); if this isn't already downloaded, this may take some time"); } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/AdvancementsCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/AdvancementsCache.java index 369967acc..d20eb11dd 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/AdvancementsCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/AdvancementsCache.java @@ -29,26 +29,20 @@ import com.github.steveice10.mc.protocol.data.game.advancement.Advancement; import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientAdvancementTabPacket; import lombok.Getter; import lombok.Setter; -import org.geysermc.common.window.SimpleFormWindow; -import org.geysermc.common.window.button.FormButton; -import org.geysermc.common.window.response.SimpleFormResponse; +import lombok.experimental.Accessors; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.chat.MessageTranslator; import org.geysermc.connector.utils.GeyserAdvancement; import org.geysermc.connector.utils.LanguageUtils; import org.geysermc.connector.utils.LocaleUtils; +import org.geysermc.cumulus.SimpleForm; +import org.geysermc.cumulus.response.SimpleFormResponse; import java.util.HashMap; import java.util.List; import java.util.Map; public class AdvancementsCache { - - // Different form IDs - public static final int ADVANCEMENTS_MENU_FORM_ID = 1341; - public static final int ADVANCEMENTS_LIST_FORM_ID = 1342; - public static final int ADVANCEMENT_INFO_FORM_ID = 1343; - /** * Stores the player's advancement progress */ @@ -64,7 +58,7 @@ public class AdvancementsCache { /** * Stores player's chosen advancement's ID and title for use in form creators. */ - @Setter + @Setter @Accessors(chain = true) private String currentAdvancementCategoryId = null; private final GeyserSession session; @@ -74,73 +68,128 @@ public class AdvancementsCache { } /** - * Build a form with all advancement categories - * - * @return The built advancement category menu + * Build and send a form with all advancement categories */ - public SimpleFormWindow buildMenuForm() { - // Cache the language for cleaner access - String language = session.getClientData().getLanguageCode(); + public void buildAndShowMenuForm() { + SimpleForm.Builder builder = + SimpleForm.builder() + .translator(LocaleUtils::getLocaleString, session.getLocale()) + .title("gui.advancements"); - // Created menu window for advancement categories - SimpleFormWindow window = new SimpleFormWindow(LocaleUtils.getLocaleString("gui.advancements", language), ""); + boolean hasAdvancements = false; for (Map.Entry advancement : storedAdvancements.entrySet()) { if (advancement.getValue().getParentId() == null) { // No parent means this is a root advancement - window.getButtons().add(new FormButton(MessageTranslator.convertMessage(advancement.getValue().getDisplayData().getTitle(), language))); + hasAdvancements = true; + builder.button(MessageTranslator.convertMessage(advancement.getValue().getDisplayData().getTitle(), session.getLocale())); } } - if (window.getButtons().isEmpty()) { - window.setContent(LocaleUtils.getLocaleString("advancements.empty", language)); + if (!hasAdvancements) { + builder.content("advancements.empty"); } - return window; + builder.responseHandler((form, responseData) -> { + SimpleFormResponse response = form.parseResponse(responseData); + if (!response.isCorrect()) { + return; + } + + String id = ""; + + int advancementIndex = 0; + for (Map.Entry advancement : storedAdvancements.entrySet()) { + if (advancement.getValue().getParentId() == null) { // Root advancement + if (advancementIndex == response.getClickedButtonId()) { + id = advancement.getKey(); + break; + } else { + advancementIndex++; + } + } + } + + if (!id.equals("")) { + if (id.equals(currentAdvancementCategoryId)) { + // The server thinks we are already on this tab + buildAndShowListForm(); + } else { + // Send a packet indicating that we intend to open this particular advancement window + ClientAdvancementTabPacket packet = new ClientAdvancementTabPacket(id); + session.sendDownstreamPacket(packet); + // Wait for a response there + } + } + }); + + session.sendForm(builder); } /** - * Builds the list of advancements - * - * @return The built list form + * Build and send the list of advancements */ - public SimpleFormWindow buildListForm() { - // Cache the language for easier access - String language = session.getLocale(); - String id = currentAdvancementCategoryId; + public void buildAndShowListForm() { GeyserAdvancement categoryAdvancement = storedAdvancements.get(currentAdvancementCategoryId); + String language = session.getLocale(); - // Create the window - SimpleFormWindow window = new SimpleFormWindow(MessageTranslator.convertMessage(categoryAdvancement.getDisplayData().getTitle(), language), - MessageTranslator.convertMessage(categoryAdvancement.getDisplayData().getDescription(), language)); + SimpleForm.Builder builder = + SimpleForm.builder() + .title(MessageTranslator.convertMessage(categoryAdvancement.getDisplayData().getTitle(), language)) + .content(MessageTranslator.convertMessage(categoryAdvancement.getDisplayData().getDescription(), language)); - if (id != null) { - for (Map.Entry advancementEntry : storedAdvancements.entrySet()) { - GeyserAdvancement advancement = advancementEntry.getValue(); + if (currentAdvancementCategoryId != null) { + for (GeyserAdvancement advancement : storedAdvancements.values()) { if (advancement != null) { if (advancement.getParentId() != null && currentAdvancementCategoryId.equals(advancement.getRootId(this))) { - boolean earned = isEarned(advancement); - - if (earned || !advancement.getDisplayData().isShowToast()) { - window.getButtons().add(new FormButton("§6" + MessageTranslator.convertMessage(advancementEntry.getValue().getDisplayData().getTitle()) + "\n")); - } else { - window.getButtons().add(new FormButton(MessageTranslator.convertMessage(advancementEntry.getValue().getDisplayData().getTitle()) + "\n")); - } + boolean color = isEarned(advancement) || !advancement.getDisplayData().isShowToast(); + builder.button((color ? "§6" : "") + MessageTranslator.convertMessage(advancement.getDisplayData().getTitle()) + '\n'); } } } } - window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("gui.back", language))); + builder.button(LanguageUtils.getPlayerLocaleString("gui.back", language)); - return window; + builder.responseHandler((form, responseData) -> { + SimpleFormResponse response = form.parseResponse(responseData); + if (!response.isCorrect()) { + // Indicate that we have closed the current advancement tab + session.sendDownstreamPacket(new ClientAdvancementTabPacket()); + return; + } + + GeyserAdvancement advancement = null; + int advancementIndex = 0; + // Loop around to find the advancement that the client pressed + for (GeyserAdvancement advancementEntry : storedAdvancements.values()) { + if (advancementEntry.getParentId() != null && + currentAdvancementCategoryId.equals(advancementEntry.getRootId(this))) { + if (advancementIndex == response.getClickedButtonId()) { + advancement = advancementEntry; + break; + } else { + advancementIndex++; + } + } + } + + if (advancement != null) { + buildAndShowInfoForm(advancement); + } else { + buildAndShowMenuForm(); + // Indicate that we have closed the current advancement tab + session.sendDownstreamPacket(new ClientAdvancementTabPacket()); + } + }); + + session.sendForm(builder); } /** * Builds the advancement display info based on the chosen category * * @param advancement The advancement used to create the info display - * @return The information for the chosen advancement */ - public SimpleFormWindow buildInfoForm(GeyserAdvancement advancement) { + public void buildAndShowInfoForm(GeyserAdvancement advancement) { // Cache language for easier access String language = session.getLocale(); @@ -160,16 +209,24 @@ public class AdvancementsCache { Parent Advancement: Minecraft // If relevant */ - String content = description + "\n\n§f" + - earnedString + "\n"; + String content = description + "\n\n§f" + earnedString + "\n"; if (!currentAdvancementCategoryId.equals(advancement.getParentId())) { // Only display the parent if it is not the category content += LanguageUtils.getPlayerLocaleString("geyser.advancements.parentid", language, MessageTranslator.convertMessage(storedAdvancements.get(advancement.getParentId()).getDisplayData().getTitle(), language)); } - SimpleFormWindow window = new SimpleFormWindow(MessageTranslator.convertMessage(advancement.getDisplayData().getTitle()), content); - window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("gui.back", language))); - return window; + session.sendForm( + SimpleForm.builder() + .title(MessageTranslator.convertMessage(advancement.getDisplayData().getTitle())) + .content(content) + .button(LanguageUtils.getPlayerLocaleString("gui.back", language)) + .responseHandler((form, responseData) -> { + SimpleFormResponse response = form.parseResponse(responseData); + if (response.isCorrect()) { + buildAndShowListForm(); + } + }) + ); } /** @@ -209,108 +266,6 @@ public class AdvancementsCache { return earned; } - /** - * Handle the menu form response - * - * @param response The response string to parse - * @return True if the form was parsed correctly, false if not - */ - public boolean handleMenuForm(String response) { - SimpleFormWindow menuForm = (SimpleFormWindow) session.getWindowCache().getWindows().get(ADVANCEMENTS_MENU_FORM_ID); - menuForm.setResponse(response); - - SimpleFormResponse formResponse = (SimpleFormResponse) menuForm.getResponse(); - - String id = ""; - if (formResponse != null && formResponse.getClickedButton() != null) { - int advancementIndex = 0; - for (Map.Entry advancement : storedAdvancements.entrySet()) { - if (advancement.getValue().getParentId() == null) { // Root advancement - if (advancementIndex == formResponse.getClickedButtonId()) { - id = advancement.getKey(); - break; - } else { - advancementIndex++; - } - } - } - } - if (!id.equals("")) { - if (id.equals(currentAdvancementCategoryId)) { - // The server thinks we are already on this tab - session.sendForm(buildListForm(), ADVANCEMENTS_LIST_FORM_ID); - } else { - // Send a packet indicating that we intend to open this particular advancement window - ClientAdvancementTabPacket packet = new ClientAdvancementTabPacket(id); - session.sendDownstreamPacket(packet); - // Wait for a response there - } - } - - return true; - } - - /** - * Handle the list form response (Advancement category choice) - * - * @param response The response string to parse - * @return True if the form was parsed correctly, false if not - */ - public boolean handleListForm(String response) { - SimpleFormWindow listForm = (SimpleFormWindow) session.getWindowCache().getWindows().get(ADVANCEMENTS_LIST_FORM_ID); - listForm.setResponse(response); - - SimpleFormResponse formResponse = (SimpleFormResponse) listForm.getResponse(); - - if (!listForm.isClosed() && formResponse != null && formResponse.getClickedButton() != null) { - GeyserAdvancement advancement = null; - int advancementIndex = 0; - // Loop around to find the advancement that the client pressed - for (GeyserAdvancement advancementEntry : storedAdvancements.values()) { - if (advancementEntry.getParentId() != null && - currentAdvancementCategoryId.equals(advancementEntry.getRootId(this))) { - if (advancementIndex == formResponse.getClickedButtonId()) { - advancement = advancementEntry; - break; - } else { - advancementIndex++; - } - } - } - if (advancement != null) { - session.sendForm(buildInfoForm(advancement), ADVANCEMENT_INFO_FORM_ID); - } else { - session.sendForm(buildMenuForm(), ADVANCEMENTS_MENU_FORM_ID); - // Indicate that we have closed the current advancement tab - session.sendDownstreamPacket(new ClientAdvancementTabPacket()); - } - } else { - // Indicate that we have closed the current advancement tab - session.sendDownstreamPacket(new ClientAdvancementTabPacket()); - } - - return true; - } - - /** - * Handle the info form response - * - * @param response The response string to parse - * @return True if the form was parsed correctly, false if not - */ - public boolean handleInfoForm(String response) { - SimpleFormWindow listForm = (SimpleFormWindow) session.getWindowCache().getWindows().get(ADVANCEMENT_INFO_FORM_ID); - listForm.setResponse(response); - - SimpleFormResponse formResponse = (SimpleFormResponse) listForm.getResponse(); - - if (!listForm.isClosed() && formResponse != null && formResponse.getClickedButton() != null) { - session.sendForm(buildListForm(), ADVANCEMENTS_LIST_FORM_ID); - } - - return true; - } - public String getColorFromAdvancementFrameType(GeyserAdvancement advancement) { String base = "\u00a7"; if (advancement.getDisplayData().getFrameType() == Advancement.DisplayData.FrameType.CHALLENGE) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaAdvancementsTabTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaAdvancementsTabTranslator.java index 17a3b3792..80b9f9155 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaAdvancementsTabTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaAdvancementsTabTranslator.java @@ -27,7 +27,6 @@ package org.geysermc.connector.network.translators.java; import com.github.steveice10.mc.protocol.packet.ingame.server.ServerAdvancementTabPacket; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.session.cache.AdvancementsCache; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; @@ -36,10 +35,10 @@ import org.geysermc.connector.network.translators.Translator; */ @Translator(packet = ServerAdvancementTabPacket.class) public class JavaAdvancementsTabTranslator extends PacketTranslator { - @Override public void translate(ServerAdvancementTabPacket packet, GeyserSession session) { - session.getAdvancementsCache().setCurrentAdvancementCategoryId(packet.getTabId()); - session.sendForm(session.getAdvancementsCache().buildListForm(), AdvancementsCache.ADVANCEMENTS_LIST_FORM_ID); + session.getAdvancementsCache() + .setCurrentAdvancementCategoryId(packet.getTabId()) + .buildAndShowListForm(); } } 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 e330cbf70..00c7aea0f 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java @@ -169,7 +169,7 @@ public class LoginEncryptionUtils { .translator(LanguageUtils::getPlayerLocaleString, session.getLocale()) .title("geyser.auth.login.form.notice.title") .content("geyser.auth.login.form.notice.desc") - .button("geyser.auth.login.form.notice.btn_login.mojang") //todo optional + .optionalButton("geyser.auth.login.form.notice.btn_login.mojang", isPasswordAuthEnabled) .button("geyser.auth.login.form.notice.btn_login.microsoft") .button("geyser.auth.login.form.notice.btn_disconnect") .responseHandler((form, responseData) -> { @@ -179,9 +179,6 @@ public class LoginEncryptionUtils { return; } - int microsoftButton = isPasswordAuthentication ? 1 : 0; - int disconnectButton = isPasswordAuthentication ? 2 : 1; - if (isPasswordAuthEnabled && response.getClickedButtonId() == 0) { session.setMicrosoftAccount(false); buildAndShowLoginDetailsWindow(session); @@ -272,6 +269,6 @@ public class LoginEncryptionUtils { session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.getLocale())); } }) - ) + ); } } From 52ddf8c556721bc97bcf16700c409ace66edd99f Mon Sep 17 00:00:00 2001 From: Tim203 Date: Fri, 12 Feb 2021 22:22:45 +0100 Subject: [PATCH 025/107] Moved skin uploading to the global api --- .../geysermc/floodgate/util/BedrockData.java | 27 ++- .../org/geysermc/floodgate/util/RawSkin.java | 106 --------- .../floodgate/util/WebsocketEventType.java | 40 ++++ connector/pom.xml | 5 + .../geysermc/connector/GeyserConnector.java | 3 + .../network/session/GeyserSession.java | 21 +- .../network/session/auth/AuthData.java | 22 +- .../session/auth/BedrockClientData.java | 67 ------ .../connector/skin/FloodgateSkinUploader.java | 205 ++++++++++++++++++ .../geysermc/connector/utils/Constants.java | 43 ++++ .../connector/utils/LoginEncryptionUtils.java | 3 +- .../connector/utils/PluginMessageUtils.java | 21 +- 12 files changed, 363 insertions(+), 200 deletions(-) delete mode 100644 common/src/main/java/org/geysermc/floodgate/util/RawSkin.java create mode 100644 common/src/main/java/org/geysermc/floodgate/util/WebsocketEventType.java create mode 100644 connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java create mode 100644 connector/src/main/java/org/geysermc/connector/utils/Constants.java diff --git a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java index 5f449fe2d..4ea5ee78e 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java +++ b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java @@ -37,7 +37,7 @@ import lombok.Getter; @Getter @AllArgsConstructor(access = AccessLevel.PRIVATE) public final class BedrockData implements Cloneable { - public static final int EXPECTED_LENGTH = 10; + public static final int EXPECTED_LENGTH = 12; private final String version; private final String username; @@ -50,19 +50,24 @@ public final class BedrockData implements Cloneable { private final LinkedPlayer linkedPlayer; private final boolean fromProxy; + private final int subscribeId; + private final String verifyCode; + private final int dataLength; public static BedrockData of(String version, String username, String xuid, int deviceOs, String languageCode, int uiProfile, int inputMode, String ip, - LinkedPlayer linkedPlayer, boolean fromProxy) { + LinkedPlayer linkedPlayer, boolean fromProxy, int subscribeId, + String verifyCode) { return new BedrockData(version, username, xuid, deviceOs, languageCode, inputMode, - uiProfile, ip, linkedPlayer, fromProxy, EXPECTED_LENGTH); + uiProfile, ip, linkedPlayer, fromProxy, subscribeId, verifyCode, EXPECTED_LENGTH); } public static BedrockData of(String version, String username, String xuid, int deviceOs, - String languageCode, int uiProfile, int inputMode, String ip) { - return of(version, username, xuid, deviceOs, languageCode, - uiProfile, inputMode, ip, null, false); + String languageCode, int uiProfile, int inputMode, String ip, + int subscribeId, String verifyCode) { + return of(version, username, xuid, deviceOs, languageCode, uiProfile, inputMode, ip, null, + false, subscribeId, verifyCode); } public static BedrockData fromString(String data) { @@ -75,13 +80,14 @@ public final class BedrockData implements Cloneable { // The format is the same as the order of the fields in this class return new BedrockData( split[0], split[1], split[2], Integer.parseInt(split[3]), split[4], - Integer.parseInt(split[5]), Integer.parseInt(split[6]), split[7], - linkedPlayer, "1".equals(split[8]), split.length + Integer.parseInt(split[5]), Integer.parseInt(split[6]), split[7], linkedPlayer, + "1".equals(split[9]), Integer.parseInt(split[10]), split[11], split.length ); } private static BedrockData emptyData(int dataLength) { - return new BedrockData(null, null, null, -1, null, -1, -1, null, null, false, dataLength); + return new BedrockData(null, null, null, -1, null, -1, -1, null, null, false, -1, null, + dataLength); } public boolean hasPlayerLink() { @@ -94,7 +100,8 @@ public final class BedrockData implements Cloneable { return version + '\0' + username + '\0' + xuid + '\0' + deviceOs + '\0' + languageCode + '\0' + uiProfile + '\0' + inputMode + '\0' + ip + '\0' + (fromProxy ? 1 : 0) + '\0' + - (linkedPlayer != null ? linkedPlayer.toString() : "null"); + (linkedPlayer != null ? linkedPlayer.toString() : "null") + '\0' + + subscribeId + '\0' + verifyCode; } @Override diff --git a/common/src/main/java/org/geysermc/floodgate/util/RawSkin.java b/common/src/main/java/org/geysermc/floodgate/util/RawSkin.java deleted file mode 100644 index 470b7e24a..000000000 --- a/common/src/main/java/org/geysermc/floodgate/util/RawSkin.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.floodgate.util; - -import lombok.AllArgsConstructor; - -import java.nio.ByteBuffer; -import java.util.Base64; - -import static java.lang.String.format; - -@AllArgsConstructor -public final class RawSkin { - public int width; - public int height; - public byte[] data; - public boolean alex; - - private RawSkin() { - } - - public static RawSkin decode(byte[] data, int offset) throws InvalidFormatException { - if (data == null || offset < 0 || data.length <= offset) { - return null; - } - if (offset == 0) { - return decode(data); - } - - byte[] rawSkin = new byte[data.length - offset]; - System.arraycopy(data, offset, rawSkin, 0, rawSkin.length); - return decode(rawSkin); - } - - public static RawSkin decode(byte[] data) throws InvalidFormatException { - // offset is an amount of bytes before the Base64 starts - if (data == null) { - return null; - } - - int maxEncodedLength = Base64Utils.getEncodedLength(64 * 64 * 4 + 9); - // if the RawSkin is longer then the max Java Edition skin length - if (data.length > maxEncodedLength) { - throw new InvalidFormatException(format( - "Encoded data cannot be longer then %s bytes! Got %s", - maxEncodedLength, data.length - )); - } - - // if the encoded data doesn't even contain the width, height (8 bytes, 2 ints) and isAlex - if (data.length < Base64Utils.getEncodedLength(9)) { - throw new InvalidFormatException("Encoded data must be at least 16 bytes long!"); - } - - data = Base64.getDecoder().decode(data); - - ByteBuffer buffer = ByteBuffer.wrap(data); - - RawSkin skin = new RawSkin(); - skin.width = buffer.getInt(); - skin.height = buffer.getInt(); - if (buffer.remaining() - 1 != (skin.width * skin.height * 4)) { - throw new InvalidFormatException(format( - "Expected skin length to be %s, got %s", - (skin.width * skin.height * 4), buffer.remaining() - )); - } - skin.data = new byte[buffer.remaining() - 1]; - buffer.get(skin.data); - skin.alex = buffer.get() == 1; - return skin; - } - - public byte[] encode() { - // 2 x int + 1 = 9 bytes - ByteBuffer buffer = ByteBuffer.allocate(9 + data.length); - buffer.putInt(width); - buffer.putInt(height); - buffer.put(data); - buffer.put((byte) (alex ? 1 : 0)); - return Base64.getEncoder().encode(buffer.array()); - } -} diff --git a/common/src/main/java/org/geysermc/floodgate/util/WebsocketEventType.java b/common/src/main/java/org/geysermc/floodgate/util/WebsocketEventType.java new file mode 100644 index 000000000..1527f2360 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/util/WebsocketEventType.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.floodgate.util; + +public enum WebsocketEventType { + SUBSCRIBER_CREATED, + SUBSCRIBERS_COUNT, + ADDED_TO_QUEUE, + SKIN_UPLOADED, + CREATOR_DISCONNECTED; + + public static final WebsocketEventType[] VALUES = values(); + + public static WebsocketEventType getById(int id) { + return VALUES.length > id ? VALUES[id] : null; + } +} diff --git a/connector/pom.xml b/connector/pom.xml index 5e78fcfc3..04fc918fa 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -22,6 +22,11 @@ 2.9.8 compile
+ + org.java-websocket + Java-Websocket + 1.5.1 + com.fasterxml.jackson.datatype jackson-datatype-jsr310 diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 38c2aa29a..4dc78df1c 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -56,6 +56,7 @@ import org.geysermc.connector.network.translators.world.WorldManager; import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.network.translators.world.block.entity.BlockEntityTranslator; import org.geysermc.connector.network.translators.world.block.entity.SkullBlockEntityTranslator; +import org.geysermc.connector.skin.FloodgateSkinUploader; import org.geysermc.connector.utils.DimensionUtils; import org.geysermc.connector.utils.LanguageUtils; import org.geysermc.connector.utils.LocaleUtils; @@ -108,6 +109,7 @@ public class GeyserConnector { private AuthType authType; private FloodgateCipher cipher; + private FloodgateSkinUploader skinUploader; private boolean shuttingDown = false; @@ -203,6 +205,7 @@ public class GeyserConnector { cipher = new AesCipher(new Base64Topping()); cipher.init(key); logger.info(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.loaded_key")); + skinUploader = new FloodgateSkinUploader(this).start(); } catch (Exception exception) { logger.severe(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.bad_key"), exception); } 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 8bdb7f2b1..722cd2e45 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 @@ -67,10 +67,10 @@ import lombok.Getter; import lombok.NonNull; import lombok.Setter; import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.entity.Tickable; import org.geysermc.connector.command.CommandSender; import org.geysermc.connector.common.AuthType; import org.geysermc.connector.entity.Entity; +import org.geysermc.connector.entity.Tickable; import org.geysermc.connector.entity.player.SessionPlayerEntity; import org.geysermc.connector.entity.player.SkullPlayerEntity; import org.geysermc.connector.inventory.PlayerInventory; @@ -85,6 +85,7 @@ import org.geysermc.connector.network.translators.chat.MessageTranslator; import org.geysermc.connector.network.translators.collision.CollisionManager; import org.geysermc.connector.network.translators.inventory.EnchantmentInventoryTranslator; import org.geysermc.connector.network.translators.item.ItemRegistry; +import org.geysermc.connector.skin.FloodgateSkinUploader; import org.geysermc.connector.skin.SkinManager; import org.geysermc.connector.utils.*; import org.geysermc.cumulus.Form; @@ -553,7 +554,9 @@ public class GeyserSession implements CommandSender { byte[] encryptedData; try { + FloodgateSkinUploader skinUploader = connector.getSkinUploader(); FloodgateCipher cipher = connector.getCipher(); + encryptedData = cipher.encryptFromString(BedrockData.of( clientData.getGameVersion(), authData.getName(), @@ -562,7 +565,9 @@ public class GeyserSession implements CommandSender { clientData.getLanguageCode(), clientData.getUiProfile().ordinal(), clientData.getCurrentInputMode().ordinal(), - upstream.getSession().getAddress().getAddress().getHostAddress() + upstream.getSession().getAddress().getAddress().getHostAddress(), + skinUploader.getId(), + skinUploader.getVerifyCode() ).toString()); } catch (Exception e) { connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.encrypt_fail"), e); @@ -570,13 +575,7 @@ public class GeyserSession implements CommandSender { return; } - byte[] rawSkin = clientData.getAndTransformImage("Skin").encode(); - byte[] finalData = new byte[encryptedData.length + rawSkin.length + 1]; - System.arraycopy(encryptedData, 0, finalData, 0, encryptedData.length); - finalData[encryptedData.length] = 0x21; // splitter - System.arraycopy(rawSkin, 0, finalData, encryptedData.length + 1, rawSkin.length); - - String finalDataString = new String(finalData, StandardCharsets.UTF_8); + String finalDataString = new String(encryptedData, StandardCharsets.UTF_8); HandshakePacket handshakePacket = event.getPacket(); event.setPacket(new HandshakePacket( @@ -639,6 +638,10 @@ public class GeyserSession implements CommandSender { if (connector.getAuthType() == AuthType.OFFLINE || playerEntity.getUuid().getMostSignificantBits() == 0) { SkinManager.handleBedrockSkin(playerEntity, clientData); } + + // We'll send the skin upload a bit after the handshake packet (aka this packet), + // because otherwise the global server returns the data too fast. + getAuthData().upload(connector); } PacketTranslatorRegistry.JAVA_TRANSLATOR.translate(event.getPacket().getClass(), event.getPacket(), GeyserSession.this); diff --git a/connector/src/main/java/org/geysermc/connector/network/session/auth/AuthData.java b/connector/src/main/java/org/geysermc/connector/network/session/auth/AuthData.java index ba9e13548..463276891 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/auth/AuthData.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/auth/AuthData.java @@ -25,16 +25,26 @@ package org.geysermc.connector.network.session.auth; -import lombok.AllArgsConstructor; +import com.fasterxml.jackson.databind.JsonNode; import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.geysermc.connector.GeyserConnector; import java.util.UUID; -@Getter -@AllArgsConstructor +@RequiredArgsConstructor public class AuthData { + @Getter private final String name; + @Getter private final UUID UUID; + @Getter private final String xboxUUID; - private String name; - private UUID UUID; - private String xboxUUID; + private final JsonNode certChainData; + private final String clientData; + + public void upload(GeyserConnector connector) { + // we can't upload the skin in LoginEncryptionUtil since the global server would return + // the skin too fast, that's why we upload it after we know for sure that the target server + // is ready to handle the result of the global server + connector.getSkinUploader().uploadSkin(certChainData, clientData); + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java b/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java index 7a7069d07..aed98b09c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java @@ -34,10 +34,8 @@ import lombok.Getter; import org.geysermc.connector.skin.SkinProvider; import org.geysermc.floodgate.util.DeviceOs; import org.geysermc.floodgate.util.InputMode; -import org.geysermc.floodgate.util.RawSkin; import org.geysermc.floodgate.util.UiProfile; -import java.awt.image.BufferedImage; import java.util.Base64; import java.util.UUID; @@ -115,77 +113,12 @@ public final class BedrockClientData { @JsonProperty(value = "ThirdPartyNameOnly") private boolean thirdPartyNameOnly; - private static RawSkin getLegacyImage(byte[] imageData, boolean alex) { - if (imageData == null) { - return null; - } - - // width * height * 4 (rgba) - switch (imageData.length) { - case 8192: - return new RawSkin(64, 32, imageData, alex); - case 16384: - return new RawSkin(64, 64, imageData, alex); - case 32768: - return new RawSkin(64, 128, imageData, alex); - case 65536: - return new RawSkin(128, 128, imageData, alex); - default: - throw new IllegalArgumentException("Unknown legacy skin size"); - } - } - public void setJsonData(JsonNode data) { if (this.jsonData == null && data != null) { this.jsonData = data; } } - /** - * Taken from https://github.com/NukkitX/Nukkit/blob/master/src/main/java/cn/nukkit/network/protocol/LoginPacket.java
- * Internally only used for Skins, but can be used for Capes too - */ - public RawSkin getImage(String name) { - if (jsonData == null || !jsonData.has(name + "Data")) { - return null; - } - - boolean alex = false; - if (name.equals("Skin")) { - alex = isAlex(); - } - - byte[] image = Base64.getDecoder().decode(jsonData.get(name + "Data").asText()); - if (jsonData.has(name + "ImageWidth") && jsonData.has(name + "ImageHeight")) { - return new RawSkin( - jsonData.get(name + "ImageWidth").asInt(), - jsonData.get(name + "ImageHeight").asInt(), - image, alex - ); - } - return getLegacyImage(image, alex); - } - - public RawSkin getAndTransformImage(String name) { - RawSkin skin = getImage(name); - if (skin != null && (skin.width > 64 || skin.height > 64)) { - BufferedImage scaledImage = - SkinProvider.imageDataToBufferedImage(skin.data, skin.width, skin.height); - - int max = Math.max(skin.width, skin.height); - while (max > 64) { - max /= 2; - scaledImage = SkinProvider.scale(scaledImage); - } - - byte[] skinData = SkinProvider.bufferedImageToImageData(scaledImage); - skin.width = scaledImage.getWidth(); - skin.height = scaledImage.getHeight(); - skin.data = skinData; - } - return skin; - } - public boolean isAlex() { try { byte[] bytes = Base64.getDecoder().decode(geometryName.getBytes(Charsets.UTF_8)); diff --git a/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java b/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java new file mode 100644 index 000000000..e710a3591 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.skin; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.Getter; +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.GeyserLogger; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.utils.Constants; +import org.geysermc.connector.utils.PluginMessageUtils; +import org.geysermc.floodgate.util.WebsocketEventType; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.handshake.ServerHandshake; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; + +import static org.geysermc.connector.utils.PluginMessageUtils.getSkinChannel; + +public final class FloodgateSkinUploader { + private final ObjectMapper JACKSON = new ObjectMapper(); + private final List skinQueue = new ArrayList<>(); + + private final GeyserLogger logger; + private final WebSocketClient client; + + @Getter private int id; + @Getter private String verifyCode; + @Getter private int subscribersCount; + + public FloodgateSkinUploader(GeyserConnector connector) { + this.logger = connector.getLogger(); + this.client = new WebSocketClient(Constants.SKIN_UPLOAD_URI) { + @Override + public void onOpen(ServerHandshake handshake) { + setConnectionLostTimeout(11); + + Iterator queueIterator = skinQueue.iterator(); + while (isOpen() && queueIterator.hasNext()) { + send(queueIterator.next()); + queueIterator.remove(); + } + } + + @Override + public void onMessage(String message) { + System.out.println(message); + // The reason why I don't like Jackson + try { + JsonNode node = JACKSON.readTree(message); + if (node.has("error")) { + logger.error("Got an error: " + node.get("error").asText()); + return; + } + + int typeId = node.get("event_id").asInt(); + WebsocketEventType type = WebsocketEventType.getById(typeId); + if (type == null) { + logger.warning(String.format( + "Got (unknown) type %s. Ensure that Geyser is on the latest version and report this issue!", + typeId)); + return; + } + + switch (type) { + case SUBSCRIBER_CREATED: + id = node.get("id").asInt(); + verifyCode = node.get("verify_code").asText(); + break; + case SUBSCRIBERS_COUNT: + subscribersCount = node.get("subscribers_count").asInt(); + break; + case SKIN_UPLOADED: + // if Geyser is the only subscriber we have send it to the server manually + if (subscribersCount != 1) { + break; + } + + String xuid = node.get("xuid").asText(); + String value = node.get("value").asText(); + String signature = node.get("signature").asText(); + ; + + GeyserSession session = connector.getPlayerByXuid(xuid); + if (session != null) { + byte[] bytes = (value + '\0' + signature) + .getBytes(StandardCharsets.UTF_8); + PluginMessageUtils.sendMessage(session, getSkinChannel(), bytes); + } + break; + } + } catch (Exception e) { + logger.error("Error while receiving a message", e); + } + } + + @Override + public void onClose(int code, String reason, boolean remote) { + if (reason != null && !reason.isEmpty()) { + // The reason why I don't like Jackson + try { + JsonNode node = JACKSON.readTree(reason); + // info means that the uploader itself did nothing wrong + if (node.has("info")) { + String info = node.get("info").asText(); + logger.debug("Got disconnected from the skin uploader: " + info); + } + // error means that the uploader did something wrong + if (node.has("error")) { + String error = node.get("error").asText(); + logger.info("Got disconnected from the skin uploader: " + error); + } + // it can't be something else then info or error, so we won't handle anything other than that. + // try to reconnect (which will make a new id and verify token) after a few seconds + reconnectLater(connector); + } catch (Exception e) { + logger.error("Error while handling onClose", e); + } + } + } + + @Override + public void onError(Exception ex) { + logger.error("Got an error", ex); + } + }; + } + + public void uploadSkin(JsonNode chainData, String clientData) { + if (chainData == null || !chainData.isArray() || clientData == null) { + return; + } + + ObjectNode node = JACKSON.createObjectNode(); + node.set("chain_data", chainData); + node.put("client_data", clientData); + + // The reason why I don't like Jackson + String jsonString; + try { + jsonString = JACKSON.writeValueAsString(node); + } catch (Exception e) { + logger.error("Failed to upload skin", e); + return; + } + + if (client.isOpen()) { + client.send(jsonString); + return; + } + skinQueue.add(jsonString); + } + + private void reconnectLater(GeyserConnector connector) { + long additionalTime = ThreadLocalRandom.current().nextInt(7); + connector.getGeneralThreadPool().schedule(() -> { + try { + if (!client.connectBlocking()) { + reconnectLater(connector); + } + } catch (InterruptedException ignored) { + reconnectLater(connector); + } + }, 8 + additionalTime, TimeUnit.SECONDS); + } + + public FloodgateSkinUploader start() { + client.connect(); + return this; + } + + public void stop() { + client.close(); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/utils/Constants.java b/connector/src/main/java/org/geysermc/connector/utils/Constants.java new file mode 100644 index 000000000..8dcd63d3e --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/utils/Constants.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.utils; + +import java.net.URI; +import java.net.URISyntaxException; + +public final class Constants { + public static final URI SKIN_UPLOAD_URI; + + static { + URI skinUploadUri = null; + try { + skinUploadUri = new URI("wss://api.geysermc.org/ws"); + } catch (URISyntaxException e) { + e.printStackTrace(); + } + SKIN_UPLOAD_URI = skinUploadUri; + } +} 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 00c7aea0f..f68ee0e4c 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java @@ -118,7 +118,8 @@ public class LoginEncryptionUtils { session.setAuthenticationData(new AuthData( extraData.get("displayName").asText(), UUID.fromString(extraData.get("identity").asText()), - extraData.get("XUID").asText() + extraData.get("XUID").asText(), + certChainData, clientData )); if (payload.get("identityPublicKey").getNodeType() != JsonNodeType.STRING) { diff --git a/connector/src/main/java/org/geysermc/connector/utils/PluginMessageUtils.java b/connector/src/main/java/org/geysermc/connector/utils/PluginMessageUtils.java index 4e0fd6074..1569002f9 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/PluginMessageUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/PluginMessageUtils.java @@ -25,12 +25,15 @@ package org.geysermc.connector.utils; +import com.github.steveice10.mc.protocol.packet.ingame.server.ServerPluginMessagePacket; import com.google.common.base.Charsets; import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.network.session.GeyserSession; import java.nio.ByteBuffer; public class PluginMessageUtils { + private static final String SKIN_CHANNEL = "floodgate:skin"; private static final byte[] GEYSER_BRAND_DATA; private static final byte[] FLOODGATE_REGISTER_DATA; @@ -42,7 +45,7 @@ public class PluginMessageUtils { .put(data) .array(); - FLOODGATE_REGISTER_DATA = "floodgate:skin\0floodgate:form".getBytes(Charsets.UTF_8); + FLOODGATE_REGISTER_DATA = (SKIN_CHANNEL + "\0floodgate:form").getBytes(Charsets.UTF_8); } /** @@ -63,6 +66,22 @@ public class PluginMessageUtils { return FLOODGATE_REGISTER_DATA; } + /** + * Returns the skin channel used in Floodgate + */ + public static String getSkinChannel() { + return SKIN_CHANNEL; + } + + public static void sendMessage(GeyserSession session, String channel, byte[] data) { + byte[] finalData = + ByteBuffer.allocate(data.length + getVarIntLength(data.length)) + .put(writeVarInt(data.length)) + .put(data) + .array(); + session.sendDownstreamPacket(new ServerPluginMessagePacket(channel, finalData)); + } + private static byte[] writeVarInt(int value) { byte[] data = new byte[getVarIntLength(value)]; int index = 0; From c79979e3e383f611a5035c1a031be3b0be424ff8 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Tue, 16 Feb 2021 18:54:04 +0100 Subject: [PATCH 026/107] Added timestamp to BedrockData --- .../org/geysermc/floodgate/util/BedrockData.java | 16 +++++++++------- .../org/geysermc/floodgate/util/InputMode.java | 2 +- .../connector/skin/FloodgateSkinUploader.java | 3 +-- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java index 4ea5ee78e..cbf49e126 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java +++ b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java @@ -26,8 +26,8 @@ package org.geysermc.floodgate.util; import lombok.AccessLevel; -import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.RequiredArgsConstructor; /** * This class contains the raw data send by Geyser to Floodgate or from Floodgate to Floodgate. This @@ -35,9 +35,9 @@ import lombok.Getter; * present in the API module of the Floodgate repo) */ @Getter -@AllArgsConstructor(access = AccessLevel.PRIVATE) +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) public final class BedrockData implements Cloneable { - public static final int EXPECTED_LENGTH = 12; + public static final int EXPECTED_LENGTH = 13; private final String version; private final String username; @@ -53,6 +53,7 @@ public final class BedrockData implements Cloneable { private final int subscribeId; private final String verifyCode; + private final long timestamp; private final int dataLength; public static BedrockData of(String version, String username, String xuid, int deviceOs, @@ -60,7 +61,8 @@ public final class BedrockData implements Cloneable { LinkedPlayer linkedPlayer, boolean fromProxy, int subscribeId, String verifyCode) { return new BedrockData(version, username, xuid, deviceOs, languageCode, inputMode, - uiProfile, ip, linkedPlayer, fromProxy, subscribeId, verifyCode, EXPECTED_LENGTH); + uiProfile, ip, linkedPlayer, fromProxy, subscribeId, verifyCode, + System.currentTimeMillis(), EXPECTED_LENGTH); } public static BedrockData of(String version, String username, String xuid, int deviceOs, @@ -81,12 +83,12 @@ public final class BedrockData implements Cloneable { return new BedrockData( split[0], split[1], split[2], Integer.parseInt(split[3]), split[4], Integer.parseInt(split[5]), Integer.parseInt(split[6]), split[7], linkedPlayer, - "1".equals(split[9]), Integer.parseInt(split[10]), split[11], split.length + "1".equals(split[9]), Integer.parseInt(split[10]), split[11], Long.parseLong(split[12]), split.length ); } private static BedrockData emptyData(int dataLength) { - return new BedrockData(null, null, null, -1, null, -1, -1, null, null, false, -1, null, + return new BedrockData(null, null, null, -1, null, -1, -1, null, null, false, -1, null, -1, dataLength); } @@ -101,7 +103,7 @@ public final class BedrockData implements Cloneable { languageCode + '\0' + uiProfile + '\0' + inputMode + '\0' + ip + '\0' + (fromProxy ? 1 : 0) + '\0' + (linkedPlayer != null ? linkedPlayer.toString() : "null") + '\0' + - subscribeId + '\0' + verifyCode; + subscribeId + '\0' + verifyCode + '\0' + timestamp; } @Override diff --git a/common/src/main/java/org/geysermc/floodgate/util/InputMode.java b/common/src/main/java/org/geysermc/floodgate/util/InputMode.java index 9664e18ae..d49d2ea84 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/InputMode.java +++ b/common/src/main/java/org/geysermc/floodgate/util/InputMode.java @@ -29,7 +29,7 @@ package org.geysermc.floodgate.util; public enum InputMode { UNKNOWN, KEYBOARD_MOUSE, - TOUCH, // I guess Touch? + TOUCH, CONTROLLER, VR; diff --git a/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java b/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java index e710a3591..991db08bc 100644 --- a/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java +++ b/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java @@ -74,7 +74,6 @@ public final class FloodgateSkinUploader { @Override public void onMessage(String message) { - System.out.println(message); // The reason why I don't like Jackson try { JsonNode node = JACKSON.readTree(message); @@ -109,7 +108,6 @@ public final class FloodgateSkinUploader { String xuid = node.get("xuid").asText(); String value = node.get("value").asText(); String signature = node.get("signature").asText(); - ; GeyserSession session = connector.getPlayerByXuid(xuid); if (session != null) { @@ -182,6 +180,7 @@ public final class FloodgateSkinUploader { } private void reconnectLater(GeyserConnector connector) { + //todo doesn't work long additionalTime = ThreadLocalRandom.current().nextInt(7); connector.getGeneralThreadPool().schedule(() -> { try { From 66867edbc3e81669c64ea752bae631585849b9cc Mon Sep 17 00:00:00 2001 From: Tim203 Date: Thu, 25 Feb 2021 02:46:34 +0100 Subject: [PATCH 027/107] Fixed missing dependency --- connector/pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/connector/pom.xml b/connector/pom.xml index 7d08b5c60..6fb2c127c 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -21,6 +21,12 @@ 1.2.0-SNAPSHOT compile
+ + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + 2.10.2 + compile + com.fasterxml.jackson.dataformat jackson-dataformat-yaml From 0832e7d65cfe8c4abb6348ac25eef679f3efa912 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Thu, 25 Feb 2021 20:55:00 +0100 Subject: [PATCH 028/107] Fixes issue when both Geyser and Floodgate are on the same server --- bootstrap/spigot/src/main/resources/plugin.yml | 2 +- .../src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bootstrap/spigot/src/main/resources/plugin.yml b/bootstrap/spigot/src/main/resources/plugin.yml index fee71ab1f..ea655f902 100644 --- a/bootstrap/spigot/src/main/resources/plugin.yml +++ b/bootstrap/spigot/src/main/resources/plugin.yml @@ -3,7 +3,7 @@ name: ${outputName}-Spigot author: ${project.organization.name} website: ${project.organization.url} version: ${project.version} -softdepend: ["ViaVersion"] +softdepend: ["ViaVersion", "floodgate"] api-version: 1.13 commands: geyser: diff --git a/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java b/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java index 950f0eb55..7d67a44a0 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java +++ b/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java @@ -57,7 +57,7 @@ public final class LinkedPlayer implements Cloneable { return new LinkedPlayer(javaUsername, javaUniqueId, bedrockId); } - static LinkedPlayer fromString(String data) { + public static LinkedPlayer fromString(String data) { String[] split = data.split(";"); if (split.length != 3) { return null; From e7eca7f7b945fdd7148234c7101f5906a4d4c492 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sat, 27 Feb 2021 13:14:40 +0100 Subject: [PATCH 029/107] Fixed reconnecting to the api --- .../connector/skin/FloodgateSkinUploader.java | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java b/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java index 991db08bc..ad3310c8d 100644 --- a/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java +++ b/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java @@ -25,6 +25,7 @@ package org.geysermc.connector.skin; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; @@ -38,6 +39,7 @@ import org.geysermc.floodgate.util.WebsocketEventType; import org.java_websocket.client.WebSocketClient; import org.java_websocket.handshake.ServerHandshake; +import java.net.ConnectException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Iterator; @@ -138,18 +140,21 @@ public final class FloodgateSkinUploader { String error = node.get("error").asText(); logger.info("Got disconnected from the skin uploader: " + error); } - // it can't be something else then info or error, so we won't handle anything other than that. - // try to reconnect (which will make a new id and verify token) after a few seconds - reconnectLater(connector); + } catch (JsonProcessingException ignored) { + // ignore invalid json } catch (Exception e) { logger.error("Error while handling onClose", e); } } + // try to reconnect (which will make a new id and verify token) after a few seconds + reconnectLater(connector); } @Override public void onError(Exception ex) { - logger.error("Got an error", ex); + if (!(ex instanceof ConnectException)) { + logger.error("Got an error", ex); + } } }; } @@ -180,17 +185,10 @@ public final class FloodgateSkinUploader { } private void reconnectLater(GeyserConnector connector) { - //todo doesn't work long additionalTime = ThreadLocalRandom.current().nextInt(7); - connector.getGeneralThreadPool().schedule(() -> { - try { - if (!client.connectBlocking()) { - reconnectLater(connector); - } - } catch (InterruptedException ignored) { - reconnectLater(connector); - } - }, 8 + additionalTime, TimeUnit.SECONDS); + // we don't have to check the result. onClose will handle that for us + connector.getGeneralThreadPool() + .schedule(client::reconnect, 8 + additionalTime, TimeUnit.SECONDS); } public FloodgateSkinUploader start() { From a2d3ccfb2f1bae1da6c1fb799ac2c1ef84f27c4a Mon Sep 17 00:00:00 2001 From: Tim203 Date: Wed, 3 Mar 2021 19:57:52 +0100 Subject: [PATCH 030/107] Whoops. Forgot to update Geyser to match recent global api changes --- .../connector/skin/FloodgateSkinUploader.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java b/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java index ad3310c8d..9f1a515a0 100644 --- a/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java +++ b/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java @@ -103,16 +103,25 @@ public final class FloodgateSkinUploader { break; case SKIN_UPLOADED: // if Geyser is the only subscriber we have send it to the server manually + // otherwise it's handled by the Floodgate plugin subscribers if (subscribersCount != 1) { break; } String xuid = node.get("xuid").asText(); - String value = node.get("value").asText(); - String signature = node.get("signature").asText(); - GeyserSession session = connector.getPlayerByXuid(xuid); + if (session != null) { + if (!node.get("success").asBoolean()) { + logger.info("Failed to upload skin for " + session.getName()); + return; + } + + JsonNode data = node.get("data"); + + String value = data.get("value").asText(); + String signature = data.get("signature").asText(); + byte[] bytes = (value + '\0' + signature) .getBytes(StandardCharsets.UTF_8); PluginMessageUtils.sendMessage(session, getSkinChannel(), bytes); From cea16270c49eb083c1c1586f119222e83ea92094 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Wed, 3 Mar 2021 21:49:42 +0100 Subject: [PATCH 031/107] Fixed typo in artifactId --- connector/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/pom.xml b/connector/pom.xml index 6fb2c127c..107b4144b 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -35,7 +35,7 @@ org.java-websocket - Java-Websocket + Java-WebSocket 1.5.1 From 4c6c397f37e0bba80a56f3e4389c1eb22ce2d1c3 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Tue, 23 Mar 2021 01:46:33 +0100 Subject: [PATCH 032/107] Fixed merge conflicts --- .../src/main/java/org/geysermc/connector/GeyserConnector.java | 2 +- .../network/translators/java/JavaJoinGameTranslator.java | 2 +- .../network/translators/java/JavaPluginMessageTranslator.java | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 3a40474f1..5e279c121 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -195,7 +195,7 @@ public class GeyserConnector { defaultAuthType = AuthType.getByName(config.getRemote().getAuthType()); - if (authType == AuthType.FLOODGATE) { + if (defaultAuthType == AuthType.FLOODGATE) { try { Key key = new AesKeyProducer().produceFrom(config.getFloodgateKeyPath()); cipher = new AesCipher(new Base64Topping()); 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 17b5087ec..3e37f9eae 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 @@ -97,7 +97,7 @@ public class JavaJoinGameTranslator extends PacketTranslator { @Override public void translate(ServerPluginMessagePacket packet, GeyserSession session) { - // The only plugin messages to listen for are Floodgate plugin messages - if (session.getConnector().getAuthType() != AuthType.FLOODGATE) { + // The only plugin messages it has to listen for are Floodgate plugin messages + if (session.getConnector().getDefaultAuthType() != AuthType.FLOODGATE) { return; } From 644ece124fbc505b8c9ee957b959108bc85765ba Mon Sep 17 00:00:00 2001 From: Tim203 Date: Tue, 23 Mar 2021 01:49:08 +0100 Subject: [PATCH 033/107] Bumped Geyser version to 1.3.0-SNAPSHOT --- 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 ++-- pom.xml | 2 +- 9 files changed, 15 insertions(+), 15 deletions(-) diff --git a/bootstrap/bungeecord/pom.xml b/bootstrap/bungeecord/pom.xml index 10855aed4..5bbff0647 100644 --- a/bootstrap/bungeecord/pom.xml +++ b/bootstrap/bungeecord/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 1.2.0-SNAPSHOT + 1.3.0-SNAPSHOT bootstrap-bungeecord @@ -14,7 +14,7 @@ org.geysermc connector - 1.2.0-SNAPSHOT + 1.3.0-SNAPSHOT compile diff --git a/bootstrap/pom.xml b/bootstrap/pom.xml index 4b61a65c3..d58cb2ca4 100644 --- a/bootstrap/pom.xml +++ b/bootstrap/pom.xml @@ -6,7 +6,7 @@ org.geysermc geyser-parent - 1.2.0-SNAPSHOT + 1.3.0-SNAPSHOT bootstrap-parent pom diff --git a/bootstrap/spigot/pom.xml b/bootstrap/spigot/pom.xml index d1cba8aea..dc9a95d5f 100644 --- a/bootstrap/spigot/pom.xml +++ b/bootstrap/spigot/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 1.2.0-SNAPSHOT + 1.3.0-SNAPSHOT bootstrap-spigot @@ -14,7 +14,7 @@ org.geysermc connector - 1.2.0-SNAPSHOT + 1.3.0-SNAPSHOT compile diff --git a/bootstrap/sponge/pom.xml b/bootstrap/sponge/pom.xml index 97c4ac8a4..adeaa91de 100644 --- a/bootstrap/sponge/pom.xml +++ b/bootstrap/sponge/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 1.2.0-SNAPSHOT + 1.3.0-SNAPSHOT bootstrap-sponge @@ -14,7 +14,7 @@ org.geysermc connector - 1.2.0-SNAPSHOT + 1.3.0-SNAPSHOT compile diff --git a/bootstrap/standalone/pom.xml b/bootstrap/standalone/pom.xml index 831239f66..6610af3b5 100644 --- a/bootstrap/standalone/pom.xml +++ b/bootstrap/standalone/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 1.2.0-SNAPSHOT + 1.3.0-SNAPSHOT bootstrap-standalone @@ -14,7 +14,7 @@ org.geysermc connector - 1.2.0-SNAPSHOT + 1.3.0-SNAPSHOT compile diff --git a/bootstrap/velocity/pom.xml b/bootstrap/velocity/pom.xml index 58eee1f77..5dadc83c4 100644 --- a/bootstrap/velocity/pom.xml +++ b/bootstrap/velocity/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 1.2.0-SNAPSHOT + 1.3.0-SNAPSHOT bootstrap-velocity @@ -14,7 +14,7 @@ org.geysermc connector - 1.2.0-SNAPSHOT + 1.3.0-SNAPSHOT compile diff --git a/common/pom.xml b/common/pom.xml index 0f4d5421b..be90f0146 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -6,7 +6,7 @@ org.geysermc geyser-parent - 1.2.0-SNAPSHOT + 1.3.0-SNAPSHOT common diff --git a/connector/pom.xml b/connector/pom.xml index 4ae7e1383..4e244dd00 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -6,7 +6,7 @@ org.geysermc geyser-parent - 1.2.0-SNAPSHOT + 1.3.0-SNAPSHOT connector @@ -20,7 +20,7 @@ org.geysermc common - 1.2.0-SNAPSHOT + 1.3.0-SNAPSHOT compile diff --git a/pom.xml b/pom.xml index 011b320f4..959b7f953 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 org.geysermc geyser-parent - 1.2.0-SNAPSHOT + 1.3.0-SNAPSHOT pom Geyser Allows for players from Minecraft Bedrock Edition to join Minecraft Java Edition servers. From 2caf811750c3b195de05ff8ca42434be6b7c74c6 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Tue, 23 Mar 2021 02:02:20 +0100 Subject: [PATCH 034/107] Deploy Floodgate 2.0 builds --- Jenkinsfile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 09e88e86e..d76a6f737 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -22,7 +22,10 @@ pipeline { stage ('Deploy') { when { - branch "master" + anyOf { + branch "master" + branch "floodgate-2.0" + } } steps { From 677a8d68f659247fa73a9b7b44d3dfe006e79d5a Mon Sep 17 00:00:00 2001 From: Tim203 Date: Wed, 24 Mar 2021 15:50:13 +0100 Subject: [PATCH 035/107] Add an additional key searching fallback for Floodgate 2.0 --- .../org/geysermc/connector/FloodgateKeyLoader.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/FloodgateKeyLoader.java b/connector/src/main/java/org/geysermc/connector/FloodgateKeyLoader.java index 9b9ee5681..7f5bca8c2 100644 --- a/connector/src/main/java/org/geysermc/connector/FloodgateKeyLoader.java +++ b/connector/src/main/java/org/geysermc/connector/FloodgateKeyLoader.java @@ -33,9 +33,18 @@ import java.nio.file.Path; public class FloodgateKeyLoader { public static Path getKeyPath(GeyserJacksonConfiguration config, Object floodgate, Path floodgateDataFolder, Path geyserDataFolder, GeyserLogger logger) { + if (!config.getRemote().getAuthType().equals("floodgate")) { + return geyserDataFolder.resolve(config.getFloodgateKeyFile()); + } + Path floodgateKey = geyserDataFolder.resolve(config.getFloodgateKeyFile()); - if (!Files.exists(floodgateKey) && config.getRemote().getAuthType().equals("floodgate")) { + if (config.getFloodgateKeyFile().equals("public-key.pem")) { + logger.info("Floodgate 2.0 doesn't use a public/private key system anymore. We'll search for key.pem instead"); + floodgateKey = geyserDataFolder.resolve("key.pem"); + } + + if (!Files.exists(floodgateKey)) { if (floodgate != null) { Path autoKey = floodgateDataFolder.resolve("key.pem"); if (Files.exists(autoKey)) { From 5c12dc8e1515b6fb67439acd5e722349d435f06c Mon Sep 17 00:00:00 2001 From: Tim203 Date: Thu, 1 Apr 2021 00:37:58 +0200 Subject: [PATCH 036/107] Don't use a blocking algorithm for generating keys on unix-like systems This should fix GeyserMC/Floodgate#125 --- .../geysermc/floodgate/crypto/AesKeyProducer.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/org/geysermc/floodgate/crypto/AesKeyProducer.java b/common/src/main/java/org/geysermc/floodgate/crypto/AesKeyProducer.java index 5217b4cf7..bb2be89f8 100644 --- a/common/src/main/java/org/geysermc/floodgate/crypto/AesKeyProducer.java +++ b/common/src/main/java/org/geysermc/floodgate/crypto/AesKeyProducer.java @@ -29,7 +29,9 @@ package org.geysermc.floodgate.crypto; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; +import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; +import java.util.Locale; public final class AesKeyProducer implements KeyProducer { public static int KEY_SIZE = 128; @@ -38,7 +40,7 @@ public final class AesKeyProducer implements KeyProducer { public SecretKey produce() { try { KeyGenerator keyGenerator = KeyGenerator.getInstance("AES"); - keyGenerator.init(KEY_SIZE, SecureRandom.getInstanceStrong()); + keyGenerator.init(KEY_SIZE, getSecureRandom()); return keyGenerator.generateKey(); } catch (Exception exception) { throw new RuntimeException(exception); @@ -53,4 +55,14 @@ public final class AesKeyProducer implements KeyProducer { throw new RuntimeException(exception); } } + + private SecureRandom getSecureRandom() throws NoSuchAlgorithmException { + // use Windows-PRNG for windows (default impl is SHA1PRNG) + // default impl for unix-like systems is NativePRNG. + if (System.getProperty("os.name").toLowerCase(Locale.ROOT).contains("win")) { + return SecureRandom.getInstance("Windows-PRNG"); + } else { + return new SecureRandom(); + } + } } From a5a849c0598566744b5e08724a5ff849c88b9636 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Thu, 1 Apr 2021 00:57:47 +0200 Subject: [PATCH 037/107] Use a better name to indicate Windows --- .../main/java/org/geysermc/floodgate/crypto/AesKeyProducer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/java/org/geysermc/floodgate/crypto/AesKeyProducer.java b/common/src/main/java/org/geysermc/floodgate/crypto/AesKeyProducer.java index bb2be89f8..59080c195 100644 --- a/common/src/main/java/org/geysermc/floodgate/crypto/AesKeyProducer.java +++ b/common/src/main/java/org/geysermc/floodgate/crypto/AesKeyProducer.java @@ -59,7 +59,7 @@ public final class AesKeyProducer implements KeyProducer { private SecureRandom getSecureRandom() throws NoSuchAlgorithmException { // use Windows-PRNG for windows (default impl is SHA1PRNG) // default impl for unix-like systems is NativePRNG. - if (System.getProperty("os.name").toLowerCase(Locale.ROOT).contains("win")) { + if (System.getProperty("os.name").startsWith("Windows")) { return SecureRandom.getInstance("Windows-PRNG"); } else { return new SecureRandom(); From 23c3db28efe9c6202c438bd0dc7fb27f9f6e782e Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sat, 3 Apr 2021 19:49:44 +0200 Subject: [PATCH 038/107] Another attempt to fix key generation --- .../geysermc/floodgate/crypto/AesKeyProducer.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/common/src/main/java/org/geysermc/floodgate/crypto/AesKeyProducer.java b/common/src/main/java/org/geysermc/floodgate/crypto/AesKeyProducer.java index 59080c195..faec0ad10 100644 --- a/common/src/main/java/org/geysermc/floodgate/crypto/AesKeyProducer.java +++ b/common/src/main/java/org/geysermc/floodgate/crypto/AesKeyProducer.java @@ -31,7 +31,6 @@ import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; -import java.util.Locale; public final class AesKeyProducer implements KeyProducer { public static int KEY_SIZE = 128; @@ -58,11 +57,18 @@ public final class AesKeyProducer implements KeyProducer { private SecureRandom getSecureRandom() throws NoSuchAlgorithmException { // use Windows-PRNG for windows (default impl is SHA1PRNG) - // default impl for unix-like systems is NativePRNG. if (System.getProperty("os.name").startsWith("Windows")) { return SecureRandom.getInstance("Windows-PRNG"); } else { - return new SecureRandom(); + try { + // NativePRNG (which should be the default on unix-systems) can still block your + // system. Even though it isn't as bad as NativePRNGBlocking, we still try to + // prevent that if possible + return SecureRandom.getInstance("NativePRNGNonBlocking"); + } catch (NoSuchAlgorithmException ignored) { + // at this point we just have to go with the default impl even if it blocks + return new SecureRandom(); + } } } } From 21c8a389e30ee9b882834d7dc8121694d5d97460 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sat, 3 Apr 2021 19:50:35 +0200 Subject: [PATCH 039/107] Fixed an issue with forwarding player links --- .../java/org/geysermc/floodgate/util/BedrockData.java | 3 +-- .../geysermc/connector/skin/FloodgateSkinUploader.java | 9 +++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java index cbf49e126..81a6307a2 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java +++ b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java @@ -101,9 +101,8 @@ public final class BedrockData implements Cloneable { // The format is the same as the order of the fields in this class return version + '\0' + username + '\0' + xuid + '\0' + deviceOs + '\0' + languageCode + '\0' + uiProfile + '\0' + inputMode + '\0' + ip + '\0' + - (fromProxy ? 1 : 0) + '\0' + (linkedPlayer != null ? linkedPlayer.toString() : "null") + '\0' + - subscribeId + '\0' + verifyCode + '\0' + timestamp; + (fromProxy ? 1 : 0) + '\0' + subscribeId + '\0' + verifyCode + '\0' + timestamp; } @Override diff --git a/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java b/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java index 9f1a515a0..d61d3b632 100644 --- a/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java +++ b/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java @@ -39,6 +39,7 @@ import org.geysermc.floodgate.util.WebsocketEventType; import org.java_websocket.client.WebSocketClient; import org.java_websocket.handshake.ServerHandshake; +import javax.net.ssl.SSLException; import java.net.ConnectException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -161,9 +162,13 @@ public final class FloodgateSkinUploader { @Override public void onError(Exception ex) { - if (!(ex instanceof ConnectException)) { - logger.error("Got an error", ex); + if (ex instanceof ConnectException || ex instanceof SSLException) { + if (logger.isDebug()) { + logger.error("[debug] Got an error", ex); + } + return; } + logger.error("Got an error", ex); } }; } From 11b10e285778dd0014b6c9d3bc06a5eb4f62301f Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sat, 17 Apr 2021 17:39:08 +0200 Subject: [PATCH 040/107] Allow skin uploader to log messages instead of closing the connection --- .../floodgate/util/WebsocketEventType.java | 3 ++- .../connector/skin/FloodgateSkinUploader.java | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/org/geysermc/floodgate/util/WebsocketEventType.java b/common/src/main/java/org/geysermc/floodgate/util/WebsocketEventType.java index 1527f2360..1025fcdba 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/WebsocketEventType.java +++ b/common/src/main/java/org/geysermc/floodgate/util/WebsocketEventType.java @@ -30,7 +30,8 @@ public enum WebsocketEventType { SUBSCRIBERS_COUNT, ADDED_TO_QUEUE, SKIN_UPLOADED, - CREATOR_DISCONNECTED; + CREATOR_DISCONNECTED, + LOG_MESSAGE; public static final WebsocketEventType[] VALUES = values(); diff --git a/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java b/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java index d61d3b632..243685188 100644 --- a/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java +++ b/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java @@ -128,6 +128,23 @@ public final class FloodgateSkinUploader { PluginMessageUtils.sendMessage(session, getSkinChannel(), bytes); } break; + case LOG_MESSAGE: + String logMessage = node.get("message").asText(); + switch (node.get("priority").asInt()) { + case -1: + logger.debug("Got debug message from skin uploader: " + logMessage); + break; + case 0: + logger.info("Got info message from skin uploader: " +logMessage); + break; + case 1: + logger.error("Got error message from skin uploader: " + logMessage); + break; + default: + logger.info(logMessage); + break; + } + break; } } catch (Exception e) { logger.error("Error while receiving a message", e); From 14894c8f27844484538ff06607826adeeaf0135c Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sat, 17 Apr 2021 17:40:53 +0200 Subject: [PATCH 041/107] Allow skin uploader to log messages instead of closing the connection --- .../org/geysermc/connector/skin/FloodgateSkinUploader.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java b/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java index 243685188..dd541864a 100644 --- a/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java +++ b/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java @@ -132,13 +132,13 @@ public final class FloodgateSkinUploader { String logMessage = node.get("message").asText(); switch (node.get("priority").asInt()) { case -1: - logger.debug("Got debug message from skin uploader: " + logMessage); + logger.debug("Got a message from skin uploader: " + logMessage); break; case 0: - logger.info("Got info message from skin uploader: " +logMessage); + logger.info("Got a message from skin uploader: " +logMessage); break; case 1: - logger.error("Got error message from skin uploader: " + logMessage); + logger.error("Got a message from skin uploader: " + logMessage); break; default: logger.info(logMessage); From e45215d1eadb5cfd1a743163f2a4790c08a9bc9a Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 24 Apr 2021 15:18:41 -0400 Subject: [PATCH 042/107] Update to support ViaVersion 4.0.0 for Spigot integration This breaks compatibility with ViaVersion 3.2.1. --- bootstrap/spigot/pom.xml | 9 ++++++++- .../platform/spigot/GeyserSpigotPlugin.java | 12 +++++------- .../world/GeyserSpigot1_11CraftingListener.java | 12 ++++++------ .../GeyserSpigot1_12NativeWorldManager.java | 2 +- .../manager/GeyserSpigot1_12WorldManager.java | 16 +++++++--------- .../GeyserSpigotLegacyNativeWorldManager.java | 9 ++++----- pom.xml | 4 ---- 7 files changed, 31 insertions(+), 33 deletions(-) diff --git a/bootstrap/spigot/pom.xml b/bootstrap/spigot/pom.xml index 12c8292e9..fcc0ee410 100644 --- a/bootstrap/spigot/pom.xml +++ b/bootstrap/spigot/pom.xml @@ -10,6 +10,13 @@ bootstrap-spigot + + + viaversion-repo + https://repo.viaversion.com + + + org.geysermc @@ -26,7 +33,7 @@ us.myles viaversion - 3.2.0 + 4.0.0-21w16a provided diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java index 671ad18cf..b34ce3198 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 @@ -46,11 +46,9 @@ import org.geysermc.platform.spigot.command.SpigotCommandSender; import org.geysermc.platform.spigot.world.GeyserSpigot1_11CraftingListener; import org.geysermc.platform.spigot.world.GeyserSpigotBlockPlaceListener; import org.geysermc.platform.spigot.world.manager.*; -import us.myles.ViaVersion.api.Pair; import us.myles.ViaVersion.api.Via; import us.myles.ViaVersion.api.data.MappingData; -import us.myles.ViaVersion.api.protocol.Protocol; -import us.myles.ViaVersion.api.protocol.ProtocolRegistry; +import us.myles.ViaVersion.api.protocol.ProtocolPathEntry; import us.myles.ViaVersion.api.protocol.ProtocolVersion; import java.io.File; @@ -137,9 +135,9 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { this.geyserCommandManager = new GeyserSpigotCommandManager(this, connector); - boolean isViaVersion = (Bukkit.getPluginManager().getPlugin("ViaVersion") != null); + boolean isViaVersion = Bukkit.getPluginManager().getPlugin("ViaVersion") != null; if (isViaVersion) { - if (!isCompatible(Via.getAPI().getVersion().replace("-SNAPSHOT", ""), "3.2.0")) { + if (!Via.getAPI().getVersion().contains("4.0.0")) { geyserLogger.warning(LanguageUtils.getLocaleStringLog("geyser.bootstrap.viaversion.too_old", "https://ci.viaversion.com/job/ViaVersion/")); isViaVersion = false; @@ -306,14 +304,14 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { */ private boolean isViaVersionNeeded() { ProtocolVersion serverVersion = getServerProtocolVersion(); - List> protocolList = ProtocolRegistry.getProtocolPath(MinecraftConstants.PROTOCOL_VERSION, + List protocolList = Via.getManager().getProtocolManager().getProtocolPath(MinecraftConstants.PROTOCOL_VERSION, serverVersion.getVersion()); if (protocolList == null) { // No translation needed! return false; } for (int i = protocolList.size() - 1; i >= 0; i--) { - MappingData mappingData = protocolList.get(i).getValue().getMappingData(); + MappingData mappingData = protocolList.get(i).getProtocol().getMappingData(); if (mappingData != null) { return true; } diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigot1_11CraftingListener.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigot1_11CraftingListener.java index 2ee6457ac..04f236345 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigot1_11CraftingListener.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigot1_11CraftingListener.java @@ -46,9 +46,9 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.item.ItemTranslator; import org.geysermc.connector.network.translators.item.RecipeRegistry; import us.myles.ViaVersion.api.Pair; +import us.myles.ViaVersion.api.Via; import us.myles.ViaVersion.api.data.MappingData; -import us.myles.ViaVersion.api.protocol.Protocol; -import us.myles.ViaVersion.api.protocol.ProtocolRegistry; +import us.myles.ViaVersion.api.protocol.ProtocolPathEntry; import us.myles.ViaVersion.api.protocol.ProtocolVersion; import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.Protocol1_13To1_12_2; @@ -68,12 +68,12 @@ public class GeyserSpigot1_11CraftingListener implements Listener { /** * The list of all protocols from the client's version to 1.13. */ - private final List> protocolList; + private final List protocolList; public GeyserSpigot1_11CraftingListener(GeyserConnector connector) { this.connector = connector; - this.mappingData1_12to1_13 = ProtocolRegistry.getProtocol(Protocol1_13To1_12_2.class).getMappingData(); - this.protocolList = ProtocolRegistry.getProtocolPath(MinecraftConstants.PROTOCOL_VERSION, + this.mappingData1_12to1_13 = Via.getManager().getProtocolManager().getProtocol(Protocol1_13To1_12_2.class).getMappingData(); + this.protocolList = Via.getManager().getProtocolManager().getProtocolPath(MinecraftConstants.PROTOCOL_VERSION, ProtocolVersion.v1_13.getVersion()); } @@ -187,7 +187,7 @@ public class GeyserSpigot1_11CraftingListener implements Listener { } for (int i = protocolList.size() - 1; i >= 0; i--) { - MappingData mappingData = protocolList.get(i).getValue().getMappingData(); + MappingData mappingData = protocolList.get(i).getProtocol().getMappingData(); if (mappingData != null) { itemId = mappingData.getNewItemId(itemId); } diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12NativeWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12NativeWorldManager.java index 02347f5de..b09197fe0 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12NativeWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12NativeWorldManager.java @@ -54,7 +54,7 @@ public class GeyserSpigot1_12NativeWorldManager extends GeyserSpigot1_12WorldMan return BlockTranslator.JAVA_AIR_ID; } // Get block entity storage - BlockStorage storage = Via.getManager().getConnection(player.getUniqueId()).get(BlockStorage.class); + BlockStorage storage = Via.getManager().getConnectionManager().getConnectedClient(player.getUniqueId()).get(BlockStorage.class); int blockId = adapter.getBlockAt(player.getWorld(), x, y, z); return getLegacyBlock(storage, blockId, x, y, z); } diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12WorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12WorldManager.java index a28eef5b4..f91e9cc1c 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12WorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12WorldManager.java @@ -33,12 +33,10 @@ import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.block.BlockTranslator; -import us.myles.ViaVersion.api.Pair; import us.myles.ViaVersion.api.Via; import us.myles.ViaVersion.api.data.MappingData; import us.myles.ViaVersion.api.minecraft.Position; -import us.myles.ViaVersion.api.protocol.Protocol; -import us.myles.ViaVersion.api.protocol.ProtocolRegistry; +import us.myles.ViaVersion.api.protocol.ProtocolPathEntry; import us.myles.ViaVersion.api.protocol.ProtocolVersion; import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.Protocol1_13To1_12_2; import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.storage.BlockStorage; @@ -60,12 +58,12 @@ public class GeyserSpigot1_12WorldManager extends GeyserSpigotWorldManager { /** * The list of all protocols from the client's version to 1.13. */ - private final List> protocolList; + private final List protocolList; public GeyserSpigot1_12WorldManager(Plugin plugin) { super(plugin, false); - this.mappingData1_12to1_13 = ProtocolRegistry.getProtocol(Protocol1_13To1_12_2.class).getMappingData(); - this.protocolList = ProtocolRegistry.getProtocolPath(CLIENT_PROTOCOL_VERSION, + this.mappingData1_12to1_13 = Via.getManager().getProtocolManager().getProtocol(Protocol1_13To1_12_2.class).getMappingData(); + this.protocolList = Via.getManager().getProtocolManager().getProtocolPath(CLIENT_PROTOCOL_VERSION, ProtocolVersion.v1_13.getVersion()); } @@ -81,7 +79,7 @@ public class GeyserSpigot1_12WorldManager extends GeyserSpigotWorldManager { return BlockTranslator.JAVA_AIR_ID; } // Get block entity storage - BlockStorage storage = Via.getManager().getConnection(player.getUniqueId()).get(BlockStorage.class); + BlockStorage storage = Via.getManager().getConnectionManager().getConnectedClient(player.getUniqueId()).get(BlockStorage.class); Block block = player.getWorld().getBlockAt(x, y, z); // Black magic that gets the old block state ID int blockId = (block.getType().getId() << 4) | (block.getData() & 0xF); @@ -109,7 +107,7 @@ public class GeyserSpigot1_12WorldManager extends GeyserSpigotWorldManager { } } for (int i = protocolList.size() - 1; i >= 0; i--) { - MappingData mappingData = protocolList.get(i).getValue().getMappingData(); + MappingData mappingData = protocolList.get(i).getProtocol().getMappingData(); if (mappingData != null) { blockId = mappingData.getNewBlockStateId(blockId); } @@ -126,7 +124,7 @@ public class GeyserSpigot1_12WorldManager extends GeyserSpigotWorldManager { } World world = player.getWorld(); // Get block entity storage - BlockStorage storage = Via.getManager().getConnection(player.getUniqueId()).get(BlockStorage.class); + BlockStorage storage = Via.getManager().getConnectionManager().getConnectedClient(player.getUniqueId()).get(BlockStorage.class); for (int blockY = 0; blockY < 16; blockY++) { // Cache-friendly iteration order for (int blockZ = 0; blockZ < 16; blockZ++) { for (int blockX = 0; blockX < 16; blockX++) { diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotLegacyNativeWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotLegacyNativeWorldManager.java index 8f407de0a..9881020b1 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotLegacyNativeWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotLegacyNativeWorldManager.java @@ -31,10 +31,9 @@ import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import it.unimi.dsi.fastutil.ints.IntList; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.platform.spigot.GeyserSpigotPlugin; -import us.myles.ViaVersion.api.Pair; +import us.myles.ViaVersion.api.Via; import us.myles.ViaVersion.api.data.MappingData; -import us.myles.ViaVersion.api.protocol.Protocol; -import us.myles.ViaVersion.api.protocol.ProtocolRegistry; +import us.myles.ViaVersion.api.protocol.ProtocolPathEntry; import us.myles.ViaVersion.api.protocol.ProtocolVersion; import java.util.List; @@ -51,13 +50,13 @@ public class GeyserSpigotLegacyNativeWorldManager extends GeyserSpigotNativeWorl IntList allBlockStates = adapter.getAllBlockStates(); oldToNewBlockId = new Int2IntOpenHashMap(allBlockStates.size()); ProtocolVersion serverVersion = plugin.getServerProtocolVersion(); - List> protocolList = ProtocolRegistry.getProtocolPath(MinecraftConstants.PROTOCOL_VERSION, + List protocolList = Via.getManager().getProtocolManager().getProtocolPath(MinecraftConstants.PROTOCOL_VERSION, serverVersion.getVersion()); for (int oldBlockId : allBlockStates) { int newBlockId = oldBlockId; // protocolList should *not* be null; we checked for that before initializing this class for (int i = protocolList.size() - 1; i >= 0; i--) { - MappingData mappingData = protocolList.get(i).getValue().getMappingData(); + MappingData mappingData = protocolList.get(i).getProtocol().getMappingData(); if (mappingData != null) { newBlockId = mappingData.getNewBlockStateId(newBlockId); } diff --git a/pom.xml b/pom.xml index 4462c3f47..68a27d2f2 100644 --- a/pom.xml +++ b/pom.xml @@ -61,10 +61,6 @@ true - - viaversion-repo - https://repo.viaversion.com - sonatype https://oss.sonatype.org/content/repositories/snapshots/ From 50bea0e18012e61d9602a1d1c128319c286cc955 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 1 May 2021 00:30:01 -0400 Subject: [PATCH 043/107] Update to the latest ViaVersion changes --- bootstrap/spigot/pom.xml | 4 ++-- .../platform/spigot/GeyserSpigotPlugin.java | 18 ++++++++++++------ .../GeyserSpigot1_11CraftingListener.java | 12 ++++++------ .../GeyserSpigot1_12NativeWorldManager.java | 4 ++-- .../manager/GeyserSpigot1_12WorldManager.java | 14 +++++++------- .../GeyserSpigotLegacyNativeWorldManager.java | 8 ++++---- 6 files changed, 33 insertions(+), 27 deletions(-) diff --git a/bootstrap/spigot/pom.xml b/bootstrap/spigot/pom.xml index fcc0ee410..1b00dfc2b 100644 --- a/bootstrap/spigot/pom.xml +++ b/bootstrap/spigot/pom.xml @@ -31,9 +31,9 @@ provided - us.myles + com.viaversion viaversion - 4.0.0-21w16a + 4.0.0-21w17a provided diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java index b34ce3198..4842d9374 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 @@ -26,6 +26,10 @@ package org.geysermc.platform.spigot; import com.github.steveice10.mc.protocol.MinecraftConstants; +import com.viaversion.viaversion.api.Via; +import com.viaversion.viaversion.api.data.MappingData; +import com.viaversion.viaversion.api.protocol.ProtocolPathEntry; +import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; import org.bukkit.Bukkit; import org.bukkit.plugin.java.JavaPlugin; import org.geysermc.common.PlatformType; @@ -46,10 +50,6 @@ import org.geysermc.platform.spigot.command.SpigotCommandSender; import org.geysermc.platform.spigot.world.GeyserSpigot1_11CraftingListener; import org.geysermc.platform.spigot.world.GeyserSpigotBlockPlaceListener; import org.geysermc.platform.spigot.world.manager.*; -import us.myles.ViaVersion.api.Via; -import us.myles.ViaVersion.api.data.MappingData; -import us.myles.ViaVersion.api.protocol.ProtocolPathEntry; -import us.myles.ViaVersion.api.protocol.ProtocolVersion; import java.io.File; import java.io.IOException; @@ -137,10 +137,16 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { boolean isViaVersion = Bukkit.getPluginManager().getPlugin("ViaVersion") != null; if (isViaVersion) { - if (!Via.getAPI().getVersion().contains("4.0.0")) { + try { + // Ensure that we have the latest 4.0.0 changes and not an older ViaVersion version + Class.forName("com.viaversion.viaversion.api.ViaManager"); + } catch (ClassNotFoundException e) { geyserLogger.warning(LanguageUtils.getLocaleStringLog("geyser.bootstrap.viaversion.too_old", - "https://ci.viaversion.com/job/ViaVersion/")); + "https://ci.viaversion.com/job/ViaVersion-DEV/")); isViaVersion = false; + if (this.geyserConfig.isDebugMode()) { + e.printStackTrace(); + } } } // Used to determine if Block.getBlockData() is present. diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigot1_11CraftingListener.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigot1_11CraftingListener.java index 04f236345..eff79c7cc 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigot1_11CraftingListener.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigot1_11CraftingListener.java @@ -34,6 +34,12 @@ import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapelessRecipeDa import com.nukkitx.protocol.bedrock.data.inventory.CraftingData; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.packet.CraftingDataPacket; +import com.viaversion.viaversion.api.Via; +import com.viaversion.viaversion.api.data.MappingData; +import com.viaversion.viaversion.api.protocol.ProtocolPathEntry; +import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; +import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.Protocol1_13To1_12_2; +import com.viaversion.viaversion.util.Pair; import org.bukkit.Bukkit; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; @@ -45,12 +51,6 @@ import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.item.ItemTranslator; import org.geysermc.connector.network.translators.item.RecipeRegistry; -import us.myles.ViaVersion.api.Pair; -import us.myles.ViaVersion.api.Via; -import us.myles.ViaVersion.api.data.MappingData; -import us.myles.ViaVersion.api.protocol.ProtocolPathEntry; -import us.myles.ViaVersion.api.protocol.ProtocolVersion; -import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.Protocol1_13To1_12_2; import java.util.*; diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12NativeWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12NativeWorldManager.java index b09197fe0..c59cb0d77 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12NativeWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12NativeWorldManager.java @@ -25,6 +25,8 @@ package org.geysermc.platform.spigot.world.manager; +import com.viaversion.viaversion.api.Via; +import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.storage.BlockStorage; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; @@ -32,8 +34,6 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.geyser.adapters.spigot.SpigotAdapters; import org.geysermc.geyser.adapters.spigot.SpigotWorldAdapter; -import us.myles.ViaVersion.api.Via; -import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.storage.BlockStorage; /** * Used with ViaVersion and pre-1.13. diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12WorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12WorldManager.java index f91e9cc1c..8aff4f720 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12WorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12WorldManager.java @@ -26,6 +26,13 @@ package org.geysermc.platform.spigot.world.manager; import com.github.steveice10.mc.protocol.data.game.chunk.Chunk; +import com.viaversion.viaversion.api.Via; +import com.viaversion.viaversion.api.data.MappingData; +import com.viaversion.viaversion.api.minecraft.Position; +import com.viaversion.viaversion.api.protocol.ProtocolPathEntry; +import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; +import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.Protocol1_13To1_12_2; +import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.storage.BlockStorage; import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.block.Block; @@ -33,13 +40,6 @@ import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.block.BlockTranslator; -import us.myles.ViaVersion.api.Via; -import us.myles.ViaVersion.api.data.MappingData; -import us.myles.ViaVersion.api.minecraft.Position; -import us.myles.ViaVersion.api.protocol.ProtocolPathEntry; -import us.myles.ViaVersion.api.protocol.ProtocolVersion; -import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.Protocol1_13To1_12_2; -import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.storage.BlockStorage; import java.util.List; diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotLegacyNativeWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotLegacyNativeWorldManager.java index 9881020b1..8ec7024d9 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotLegacyNativeWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotLegacyNativeWorldManager.java @@ -26,15 +26,15 @@ package org.geysermc.platform.spigot.world.manager; import com.github.steveice10.mc.protocol.MinecraftConstants; +import com.viaversion.viaversion.api.Via; +import com.viaversion.viaversion.api.data.MappingData; +import com.viaversion.viaversion.api.protocol.ProtocolPathEntry; +import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; import it.unimi.dsi.fastutil.ints.Int2IntMap; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import it.unimi.dsi.fastutil.ints.IntList; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.platform.spigot.GeyserSpigotPlugin; -import us.myles.ViaVersion.api.Via; -import us.myles.ViaVersion.api.data.MappingData; -import us.myles.ViaVersion.api.protocol.ProtocolPathEntry; -import us.myles.ViaVersion.api.protocol.ProtocolVersion; import java.util.List; From e692b53c3faf833411f880064aab762b30dee70f Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 1 May 2021 23:19:49 -0400 Subject: [PATCH 044/107] Initial update for 21w17a --- connector/pom.xml | 2 +- .../connector/entity/type/EntityType.java | 1 + .../connector/inventory/Inventory.java | 8 - .../connector/network/BedrockProtocol.java | 10 - .../network/UpstreamPacketHandler.java | 7 +- .../network/session/GeyserSession.java | 5 +- .../inventory/click/ClickPlan.java | 20 +- .../translators/AnvilInventoryTranslator.java | 62 +- .../java/JavaJoinGameTranslator.java | 2 +- .../JavaEntitySetPassengersTranslator.java | 14 +- ...r.java => JavaRemoveEntityTranslator.java} | 17 +- .../title/JavaClearTitlesTranslator.java} | 42 +- .../JavaSetActionBarTextTranslator.java} | 50 +- .../title/JavaSetSubtitleTextTranslator.java | 52 + .../title/JavaSetTitleTextTranslator.java | 52 + .../JavaSetTitlesAnimationTranslator.java} | 26 +- .../java/world/JavaChunkDataTranslator.java | 18 +- .../world/block/BlockTranslator.java | 6 +- .../geysermc/connector/utils/ChunkUtils.java | 38 +- .../bedrock/blockpalette.1_16_100.nbt | Bin 104176 -> 0 bytes .../bedrock/blockpalette.1_16_210.nbt | Bin 34796 -> 36482 bytes .../resources/bedrock/creative_items.json | 1979 +++++++++-------- .../bedrock/runtime_item_states.json | 810 ++++--- 23 files changed, 1806 insertions(+), 1415 deletions(-) rename connector/src/main/java/org/geysermc/connector/network/translators/java/entity/{JavaEntityDestroyTranslator.java => JavaRemoveEntityTranslator.java} (75%) rename connector/src/main/java/org/geysermc/connector/network/translators/{world/block/BlockTranslator1_16_100.java => java/title/JavaClearTitlesTranslator.java} (52%) rename connector/src/main/java/org/geysermc/connector/network/translators/java/{JavaTitleTranslator.java => title/JavaSetActionBarTextTranslator.java} (52%) create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/java/title/JavaSetSubtitleTextTranslator.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/java/title/JavaSetTitleTextTranslator.java rename connector/src/main/java/org/geysermc/connector/network/translators/java/{window/JavaConfirmTransactionTranslator.java => title/JavaSetTitlesAnimationTranslator.java} (62%) delete mode 100644 connector/src/main/resources/bedrock/blockpalette.1_16_100.nbt diff --git a/connector/pom.xml b/connector/pom.xml index 90ee8a5c9..81c3b27d9 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -122,7 +122,7 @@ com.github.steveice10 mcprotocollib - 26201a4 + 21w17a-SNAPSHOT compile 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 43658f63b..a1fac77f6 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 @@ -161,6 +161,7 @@ public enum EntityType { ZOGLIN(ZoglinEntity.class, 126, 1.4f, 1.3965f, 1.3965f, 0f, "minecraft:zoglin"), PIGLIN(PiglinEntity.class, 123, 1.95f, 0.6f, 0.6f, 0f, "minecraft:piglin"), PIGLIN_BRUTE(BasePiglinEntity.class, 127, 1.95f, 0.6f, 0.6f, 0f, "minecraft:piglin_brute"), + //TODO: GOAT AXOLOTL GLOW_SQUID GLOW_ITEM_FRAME MARKER /** * Item frames are handled differently since they are a block in Bedrock. diff --git a/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java b/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java index 11a0034ad..664781763 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java @@ -66,9 +66,6 @@ public class Inventory { @Setter protected long holderId = -1; - @Getter - protected short transactionId = 0; - @Getter @Setter private boolean pending = false; @@ -114,10 +111,6 @@ public class Inventory { } } - public short getNextTransactionId() { - return ++transactionId; - } - @Override public String toString() { return "Inventory{" + @@ -127,7 +120,6 @@ public class Inventory { ", items=" + Arrays.toString(items) + ", holderPosition=" + holderPosition + ", holderId=" + holderId + - ", transactionId=" + transactionId + '}'; } } diff --git a/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java b/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java index b7365c05d..234b85884 100644 --- a/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java +++ b/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java @@ -26,9 +26,6 @@ package org.geysermc.connector.network; import com.nukkitx.protocol.bedrock.BedrockPacketCodec; -import com.nukkitx.protocol.bedrock.v419.Bedrock_v419; -import com.nukkitx.protocol.bedrock.v422.Bedrock_v422; -import com.nukkitx.protocol.bedrock.v428.Bedrock_v428; import com.nukkitx.protocol.bedrock.v431.Bedrock_v431; import java.util.ArrayList; @@ -49,13 +46,6 @@ public class BedrockProtocol { public static final List SUPPORTED_BEDROCK_CODECS = new ArrayList<>(); static { - SUPPORTED_BEDROCK_CODECS.add(Bedrock_v419.V419_CODEC.toBuilder() - .minecraftVersion("1.16.100/1.16.101") // We change this as 1.16.100.60 is a beta - .build()); - SUPPORTED_BEDROCK_CODECS.add(Bedrock_v422.V422_CODEC.toBuilder() - .minecraftVersion("1.16.200/1.16.201") - .build()); - SUPPORTED_BEDROCK_CODECS.add(Bedrock_v428.V428_CODEC); SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC); } 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 a30de4d07..17446effc 100644 --- a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java @@ -30,7 +30,6 @@ import com.nukkitx.protocol.bedrock.BedrockPacketCodec; import com.nukkitx.protocol.bedrock.data.ExperimentData; import com.nukkitx.protocol.bedrock.data.ResourcePackType; import com.nukkitx.protocol.bedrock.packet.*; -import com.nukkitx.protocol.bedrock.v428.Bedrock_v428; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.common.AuthType; import org.geysermc.connector.configuration.GeyserConfiguration; @@ -38,7 +37,6 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.cache.AdvancementsCache; import org.geysermc.connector.network.translators.PacketTranslatorRegistry; import org.geysermc.connector.network.translators.item.ItemRegistry; -import org.geysermc.connector.network.translators.world.block.BlockTranslator1_16_100; import org.geysermc.connector.network.translators.world.block.BlockTranslator1_16_210; import org.geysermc.connector.utils.*; @@ -74,8 +72,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { session.getUpstream().getSession().setPacketCodec(packetCodec); // Set the block translation based off of version - session.setBlockTranslator(packetCodec.getProtocolVersion() >= Bedrock_v428.V428_CODEC.getProtocolVersion() - ? BlockTranslator1_16_210.INSTANCE : BlockTranslator1_16_100.INSTANCE); + session.setBlockTranslator(BlockTranslator1_16_210.INSTANCE); LoginEncryptionUtils.encryptPlayerConnection(connector, session, loginPacket); @@ -140,6 +137,8 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { stackPacket.getExperiments().add(new ExperimentData("data_driven_items", true)); } + stackPacket.getExperiments().add(new ExperimentData("caves_and_cliffs", true)); + session.sendUpstreamPacket(stackPacket); break; diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 63cdc6ece..39d0f05c9 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 @@ -807,7 +807,7 @@ public class GeyserSession implements CommandSender { @Override public void packetError(PacketErrorEvent event) { connector.getLogger().warning(LanguageUtils.getLocaleStringLog("geyser.network.downstream_error", event.getCause().getMessage())); - if (connector.getConfig().isDebugMode()) + //if (connector.getConfig().isDebugMode()) //TODO don't leave this uncommented event.getCause().printStackTrace(); event.setSuppress(true); } @@ -1053,13 +1053,14 @@ public class GeyserSession implements CommandSender { startGamePacket.setItemEntries(ItemRegistry.ITEMS); startGamePacket.setVanillaVersion("*"); startGamePacket.setInventoriesServerAuthoritative(true); - startGamePacket.setAuthoritativeMovementMode(AuthoritativeMovementMode.CLIENT); // can be removed once 1.16.200 support is dropped SyncedPlayerMovementSettings settings = new SyncedPlayerMovementSettings(); settings.setMovementMode(AuthoritativeMovementMode.CLIENT); settings.setRewindHistorySize(0); settings.setServerAuthoritativeBlockBreaking(false); startGamePacket.setPlayerMovementSettings(settings); + + startGamePacket.getExperiments().add(new ExperimentData("caves_and_cliffs", true)); upstream.sendPacket(startGamePacket); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/ClickPlan.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/ClickPlan.java index c750baf51..5204bf675 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/ClickPlan.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/ClickPlan.java @@ -27,7 +27,6 @@ package org.geysermc.connector.network.translators.inventory.click; import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.github.steveice10.mc.protocol.data.game.window.WindowAction; -import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientConfirmTransactionPacket; import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientWindowActionPacket; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; @@ -43,7 +42,9 @@ import org.geysermc.connector.network.translators.inventory.translators.Crafting import org.geysermc.connector.network.translators.inventory.translators.PlayerInventoryTranslator; import org.geysermc.connector.utils.InventoryUtils; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.ListIterator; public class ClickPlan { private final List plan = new ArrayList<>(); @@ -116,22 +117,23 @@ public class ClickPlan { clickedItemStack = getItem(action.slot).getItemStack(); } - short actionId = inventory.getNextTransactionId(); + Int2ObjectMap affectedSlots = new Int2ObjectOpenHashMap<>(); + for (Int2ObjectMap.Entry simulatedSlot : simulatedItems.int2ObjectEntrySet()) { + affectedSlots.put(simulatedSlot.getIntKey(), simulatedSlot.getValue().getItemStack()); + } + ClientWindowActionPacket clickPacket = new ClientWindowActionPacket( inventory.getId(), - actionId, action.slot, - clickedItemStack, action.click.windowAction, - action.click.actionParam + action.click.actionParam, + clickedItemStack, + affectedSlots ); simulateAction(action); session.sendDownstreamPacket(clickPacket); - if (clickedItemStack == InventoryUtils.REFRESH_ITEM || action.force) { - session.sendDownstreamPacket(new ClientConfirmTransactionPacket(inventory.getId(), actionId, true)); - } } session.getPlayerInventory().setCursor(simulatedCursor, session); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AnvilInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AnvilInventoryTranslator.java index 38a0935e6..1f36eaabe 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AnvilInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AnvilInventoryTranslator.java @@ -26,21 +26,14 @@ package org.geysermc.connector.network.translators.inventory.translators; import com.github.steveice10.mc.protocol.data.game.window.WindowType; -import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientRenameItemPacket; -import com.nukkitx.nbt.NbtMap; -import com.nukkitx.protocol.bedrock.data.inventory.*; -import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.CraftResultsDeprecatedStackRequestActionData; -import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionData; -import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionType; -import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; import org.geysermc.connector.inventory.AnvilContainer; -import org.geysermc.connector.inventory.GeyserItemStack; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.inventory.PlayerInventory; -import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot; import org.geysermc.connector.network.translators.inventory.updater.UIInventoryUpdater; -import org.geysermc.connector.network.translators.item.ItemTranslator; public class AnvilInventoryTranslator extends AbstractBlockInventoryTranslator { public AnvilInventoryTranslator() { @@ -48,55 +41,6 @@ public class AnvilInventoryTranslator extends AbstractBlockInventoryTranslator { "minecraft:chipped_anvil", "minecraft:damaged_anvil"); } - /* 1.16.100 support start */ - @Override - @Deprecated - public boolean shouldHandleRequestFirst(StackRequestActionData action, Inventory inventory) { - return action.getType() == StackRequestActionType.CRAFT_NON_IMPLEMENTED_DEPRECATED; - } - - @Override - @Deprecated - public ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { - if (!(request.getActions()[1] instanceof CraftResultsDeprecatedStackRequestActionData)) { - // Just silently log an error - session.getConnector().getLogger().debug("Something isn't quite right with taking an item out of an anvil."); - return translateRequest(session, inventory, request); - } - CraftResultsDeprecatedStackRequestActionData actionData = (CraftResultsDeprecatedStackRequestActionData) request.getActions()[1]; - ItemData resultItem = actionData.getResultItems()[0]; - if (resultItem.getTag() != null) { - NbtMap displayTag = resultItem.getTag().getCompound("display"); - if (displayTag != null && displayTag.containsKey("Name")) { - ItemData sourceSlot = inventory.getItem(0).getItemData(session); - - if (sourceSlot.getTag() != null) { - NbtMap oldDisplayTag = sourceSlot.getTag().getCompound("display"); - if (oldDisplayTag != null && oldDisplayTag.containsKey("Name")) { - if (!displayTag.getString("Name").equals(oldDisplayTag.getString("Name"))) { - // Name has changed - sendRenamePacket(session, inventory, resultItem, displayTag.getString("Name")); - } - } else { - // No display tag on the old item - sendRenamePacket(session, inventory, resultItem, displayTag.getString("Name")); - } - } else { - // New NBT tag - sendRenamePacket(session, inventory, resultItem, displayTag.getString("Name")); - } - } - } - return translateRequest(session, inventory, request); - } - - private void sendRenamePacket(GeyserSession session, Inventory inventory, ItemData outputItem, String name) { - session.sendDownstreamPacket(new ClientRenameItemPacket(name)); - inventory.setItem(2, GeyserItemStack.from(ItemTranslator.translateToJava(outputItem)), session); - } - - /* 1.16.100 support end */ - @Override public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) { switch (slotInfoData.getContainer()) { 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 abd79437f..74093eecc 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 @@ -93,7 +93,7 @@ public class JavaJoinGameTranslator extends PacketTranslator= Bedrock_v428.V428_CODEC.getProtocolVersion()) { - passenger.getMetadata().put(EntityData.RIDER_MIN_ROTATION, 1f); - passenger.getMetadata().put(EntityData.RIDER_ROTATION_OFFSET, -90f); - } else { - passenger.getMetadata().put(EntityData.RIDER_MIN_ROTATION, -90f); - } + passenger.getMetadata().put(EntityData.RIDER_MIN_ROTATION, 1f); + passenger.getMetadata().put(EntityData.RIDER_ROTATION_OFFSET, -90f); } else { passenger.getMetadata().put(EntityData.RIDER_ROTATION_LOCKED, (byte) 0); passenger.getMetadata().put(EntityData.RIDER_MAX_ROTATION, 0f); @@ -123,9 +117,7 @@ public class JavaEntitySetPassengersTranslator extends PacketTranslator= Bedrock_v428.V428_CODEC.getProtocolVersion()) { - passenger.getMetadata().put(EntityData.RIDER_ROTATION_OFFSET, 0f); - } + passenger.getMetadata().put(EntityData.RIDER_ROTATION_OFFSET, 0f); this.updateOffset(passenger, entity, session, false, false, (packet.getPassengerIds().length > 1)); } else { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityDestroyTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaRemoveEntityTranslator.java similarity index 75% rename from connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityDestroyTranslator.java rename to connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaRemoveEntityTranslator.java index 1bcf9f340..a891cb323 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityDestroyTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaRemoveEntityTranslator.java @@ -25,24 +25,21 @@ package org.geysermc.connector.network.translators.java.entity; +import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerRemoveEntityPacket; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; -import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityDestroyPacket; - -@Translator(packet = ServerEntityDestroyPacket.class) -public class JavaEntityDestroyTranslator extends PacketTranslator { +@Translator(packet = ServerRemoveEntityPacket.class) +public class JavaRemoveEntityTranslator extends PacketTranslator { @Override - public void translate(ServerEntityDestroyPacket packet, GeyserSession session) { - for (int entityId : packet.getEntityIds()) { - Entity entity = session.getEntityCache().getEntityByJavaId(entityId); + public void translate(ServerRemoveEntityPacket packet, GeyserSession session) { + Entity entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId()); - if (entity != null) { - session.getEntityCache().removeEntity(entity, false); - } + if (entity != null) { + session.getEntityCache().removeEntity(entity, false); } } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator1_16_100.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/title/JavaClearTitlesTranslator.java similarity index 52% rename from connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator1_16_100.java rename to connector/src/main/java/org/geysermc/connector/network/translators/java/title/JavaClearTitlesTranslator.java index e10a503ea..e12276b1f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator1_16_100.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/title/JavaClearTitlesTranslator.java @@ -23,37 +23,23 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.world.block; +package org.geysermc.connector.network.translators.java.title; -import com.google.common.collect.ImmutableSet; -import com.nukkitx.nbt.NbtMapBuilder; +import com.github.steveice10.mc.protocol.packet.ingame.server.title.ServerClearTitlesPacket; +import com.nukkitx.protocol.bedrock.packet.SetTitlePacket; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; -import java.util.Set; - -public class BlockTranslator1_16_100 extends BlockTranslator { - private static final Set CORRECTED_STATES = ImmutableSet.of("minecraft:stripped_warped_stem", - "minecraft:stripped_warped_hyphae", "minecraft:stripped_crimson_stem", "minecraft:stripped_crimson_hyphae"); - - public static final BlockTranslator1_16_100 INSTANCE = new BlockTranslator1_16_100(); - - public BlockTranslator1_16_100() { - super("bedrock/blockpalette.1_16_100.nbt"); - } +@Translator(packet = ServerClearTitlesPacket.class) +public class JavaClearTitlesTranslator extends PacketTranslator { @Override - public int getBlockStateVersion() { - return 17825808; - } - - @Override - protected NbtMapBuilder adjustBlockStateForVersion(String bedrockIdentifier, NbtMapBuilder statesBuilder) { - if (CORRECTED_STATES.contains(bedrockIdentifier)) { - statesBuilder.putInt("deprecated", 0); - } - return super.adjustBlockStateForVersion(bedrockIdentifier, statesBuilder); - } - - public static void init() { - // no-op + public void translate(ServerClearTitlesPacket packet, GeyserSession session) { + SetTitlePacket titlePacket = new SetTitlePacket(); + // TODO handle packet.isResetTimes() + titlePacket.setType(SetTitlePacket.Type.CLEAR); + titlePacket.setText(""); + session.sendUpstreamPacket(titlePacket); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaTitleTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/title/JavaSetActionBarTextTranslator.java similarity index 52% rename from connector/src/main/java/org/geysermc/connector/network/translators/java/JavaTitleTranslator.java rename to connector/src/main/java/org/geysermc/connector/network/translators/java/title/JavaSetActionBarTextTranslator.java index ffda57826..a659a3fb2 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaTitleTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/title/JavaSetActionBarTextTranslator.java @@ -23,58 +23,30 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.java; +package org.geysermc.connector.network.translators.java.title; +import com.github.steveice10.mc.protocol.packet.ingame.server.title.ServerSetActionBarTextPacket; +import com.nukkitx.protocol.bedrock.packet.SetTitlePacket; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.network.translators.chat.MessageTranslator; -import com.github.steveice10.mc.protocol.packet.ingame.server.ServerTitlePacket; -import com.nukkitx.protocol.bedrock.packet.SetTitlePacket; - -@Translator(packet = ServerTitlePacket.class) -public class JavaTitleTranslator extends PacketTranslator { +@Translator(packet = ServerSetActionBarTextPacket.class) +public class JavaSetActionBarTextTranslator extends PacketTranslator { @Override - public void translate(ServerTitlePacket packet, GeyserSession session) { - SetTitlePacket titlePacket = new SetTitlePacket(); - String locale = session.getLocale(); - + public void translate(ServerSetActionBarTextPacket packet, GeyserSession session) { String text; - if (packet.getTitle() == null) { + if (packet.getText() == null) { //TODO 1.17 can this happen? text = " "; } else { - text = MessageTranslator.convertMessage(packet.getTitle(), locale); - } - - switch (packet.getAction()) { - case TITLE: - titlePacket.setType(SetTitlePacket.Type.TITLE); - titlePacket.setText(text); - break; - case SUBTITLE: - titlePacket.setType(SetTitlePacket.Type.SUBTITLE); - titlePacket.setText(text); - break; - case CLEAR: - case RESET: - titlePacket.setType(SetTitlePacket.Type.CLEAR); - titlePacket.setText(""); - break; - case ACTION_BAR: - titlePacket.setType(SetTitlePacket.Type.ACTIONBAR); - titlePacket.setText(text); - break; - case TIMES: - titlePacket.setType(SetTitlePacket.Type.TIMES); - titlePacket.setFadeInTime(packet.getFadeIn()); - titlePacket.setFadeOutTime(packet.getFadeOut()); - titlePacket.setStayTime(packet.getStay()); - titlePacket.setText(""); - break; + text = MessageTranslator.convertMessage(packet.getText(), session.getLocale()); } + SetTitlePacket titlePacket = new SetTitlePacket(); + titlePacket.setType(SetTitlePacket.Type.ACTIONBAR); + titlePacket.setText(text); session.sendUpstreamPacket(titlePacket); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/title/JavaSetSubtitleTextTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/title/JavaSetSubtitleTextTranslator.java new file mode 100644 index 000000000..d0f97cc3d --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/title/JavaSetSubtitleTextTranslator.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.java.title; + +import com.github.steveice10.mc.protocol.packet.ingame.server.title.ServerSetSubtitleTextPacket; +import com.nukkitx.protocol.bedrock.packet.SetTitlePacket; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; +import org.geysermc.connector.network.translators.chat.MessageTranslator; + +@Translator(packet = ServerSetSubtitleTextPacket.class) +public class JavaSetSubtitleTextTranslator extends PacketTranslator { + + @Override + public void translate(ServerSetSubtitleTextPacket packet, GeyserSession session) { + String text; + if (packet.getText() == null) { //TODO 1.17 can this happen? + text = " "; + } else { + text = MessageTranslator.convertMessage(packet.getText(), session.getLocale()); + } + + SetTitlePacket titlePacket = new SetTitlePacket(); + titlePacket.setType(SetTitlePacket.Type.SUBTITLE); + titlePacket.setText(text); + session.sendUpstreamPacket(titlePacket); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/title/JavaSetTitleTextTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/title/JavaSetTitleTextTranslator.java new file mode 100644 index 000000000..7cf06a4f7 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/title/JavaSetTitleTextTranslator.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.java.title; + +import com.github.steveice10.mc.protocol.packet.ingame.server.title.ServerSetTitleTextPacket; +import com.nukkitx.protocol.bedrock.packet.SetTitlePacket; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; +import org.geysermc.connector.network.translators.chat.MessageTranslator; + +@Translator(packet = ServerSetTitleTextPacket.class) +public class JavaSetTitleTextTranslator extends PacketTranslator { + + @Override + public void translate(ServerSetTitleTextPacket packet, GeyserSession session) { + String text; + if (packet.getText() == null) { //TODO 1.17 can this happen? + text = " "; + } else { + text = MessageTranslator.convertMessage(packet.getText(), session.getLocale()); + } + + SetTitlePacket titlePacket = new SetTitlePacket(); + titlePacket.setType(SetTitlePacket.Type.TITLE); + titlePacket.setText(text); + session.sendUpstreamPacket(titlePacket); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaConfirmTransactionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/title/JavaSetTitlesAnimationTranslator.java similarity index 62% rename from connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaConfirmTransactionTranslator.java rename to connector/src/main/java/org/geysermc/connector/network/translators/java/title/JavaSetTitlesAnimationTranslator.java index 3b55733bf..9e2c13eb5 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaConfirmTransactionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/title/JavaSetTitlesAnimationTranslator.java @@ -23,25 +23,25 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.java.window; +package org.geysermc.connector.network.translators.java.title; -import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientConfirmTransactionPacket; -import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerConfirmTransactionPacket; -import org.geysermc.connector.inventory.Inventory; +import com.github.steveice10.mc.protocol.packet.ingame.server.title.ServerSetTitlesAnimationPacket; +import com.nukkitx.protocol.bedrock.packet.SetTitlePacket; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; -@Translator(packet = ServerConfirmTransactionPacket.class) -public class JavaConfirmTransactionTranslator extends PacketTranslator { +@Translator(packet = ServerSetTitlesAnimationPacket.class) +public class JavaSetTitlesAnimationTranslator extends PacketTranslator { @Override - public void translate(ServerConfirmTransactionPacket packet, GeyserSession session) { - session.addInventoryTask(() -> { - if (!packet.isAccepted()) { - ClientConfirmTransactionPacket confirmPacket = new ClientConfirmTransactionPacket(packet.getWindowId(), packet.getActionId(), true); - session.sendDownstreamPacket(confirmPacket); - } - }); + public void translate(ServerSetTitlesAnimationPacket packet, GeyserSession session) { + SetTitlePacket titlePacket = new SetTitlePacket(); + titlePacket.setType(SetTitlePacket.Type.TIMES); + titlePacket.setText(""); + titlePacket.setFadeInTime(packet.getFadeIn()); + titlePacket.setFadeOutTime(packet.getFadeOut()); + titlePacket.setStayTime(packet.getStay()); + session.sendUpstreamPacket(titlePacket); } } 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 b6ad3dfb8..482acf59d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaChunkDataTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaChunkDataTranslator.java @@ -45,14 +45,6 @@ import org.geysermc.connector.utils.ChunkUtils; @Translator(packet = ServerChunkDataPacket.class) public class JavaChunkDataTranslator extends PacketTranslator { - /** - * Determines if we should process non-full chunks - */ - private final boolean cacheChunks; - - public JavaChunkDataTranslator() { - cacheChunks = GeyserConnector.getInstance().getConfig().isCacheChunks(); - } @Override public void translate(ServerChunkDataPacket packet, GeyserSession session) { @@ -60,23 +52,15 @@ public class JavaChunkDataTranslator extends PacketTranslator { try { - ChunkUtils.ChunkData chunkData = ChunkUtils.translateToBedrock(session, mergedColumn, isNonFullChunk); + ChunkUtils.ChunkData chunkData = ChunkUtils.translateToBedrock(session, mergedColumn); ChunkSection[] sections = chunkData.getSections(); // Find highest section 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 1a2624a6f..3bba7a478 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 @@ -229,7 +229,6 @@ public abstract class BlockTranslator { } JAVA_WATER_ID = waterRuntimeId; - BlockTranslator1_16_100.init(); BlockTranslator1_16_210.init(); BLOCKS_JSON = null; // We no longer require this so let it garbage collect away } @@ -274,7 +273,10 @@ public abstract class BlockTranslator { NbtMap blockTag = buildBedrockState(entry.getValue()); int bedrockRuntimeId = blockStateOrderedMap.getOrDefault(blockTag, -1); if (bedrockRuntimeId == -1) { - throw new RuntimeException("Unable to find " + javaId + " Bedrock runtime ID! Built compound tag: \n" + blockTag); + //TODO REMOVE THIS COMMENT BEFORE RELEASE!!!! :) + //throw new RuntimeException("Unable to find " + javaId + " Bedrock runtime ID! Built compound tag: \n" + blockTag); + bedrockRuntimeId = 0; + GeyserConnector.getInstance().getLogger().warning("Unable to find " + javaId + " Bedrock runtime ID!"); } switch (javaId) { 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 b6e387237..09d446374 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java @@ -81,7 +81,7 @@ public class ChunkUtils { return (yzx >> 8) | (yzx & 0x0F0) | ((yzx & 0x00F) << 8); } - public static ChunkData translateToBedrock(GeyserSession session, Column column, boolean isNonFullChunk) { + public static ChunkData translateToBedrock(GeyserSession session, Column column) { Chunk[] javaSections = column.getChunks(); ChunkSection[] sections = new ChunkSection[javaSections.length]; @@ -91,45 +91,11 @@ public class ChunkUtils { BitSet waterloggedPaletteIds = new BitSet(); BitSet pistonOrFlowerPaletteIds = new BitSet(); - boolean worldManagerHasMoreBlockDataThanCache = session.getConnector().getWorldManager().hasOwnChunkCache(); - - // If the received packet was a full chunk update, null sections in the chunk are guaranteed to also be null in the world manager - boolean shouldCheckWorldManagerOnMissingSections = isNonFullChunk && worldManagerHasMoreBlockDataThanCache; - Chunk temporarySection = null; - for (int sectionY = 0; sectionY < javaSections.length; sectionY++) { Chunk javaSection = javaSections[sectionY]; - // Section is null, the cache will not contain anything of use - if (javaSection == null) { - // The column parameter contains all data currently available from the cache. If the chunk is null and the world manager - // reports the ability to access more data than the cache, attempt to fetch from the world manager instead. - if (shouldCheckWorldManagerOnMissingSections) { - // Ensure that temporary chunk is set - if (temporarySection == null) { - temporarySection = new Chunk(); - } - - // Read block data in section - session.getConnector().getWorldManager().getBlocksInSection(session, column.getX(), sectionY, column.getZ(), temporarySection); - - if (temporarySection.isEmpty()) { - // The world manager only contains air for the given section - // We can leave temporarySection as-is to allow it to potentially be re-used for later sections - continue; - } else { - javaSection = temporarySection; - - // Section contents have been modified, we can't re-use it - temporarySection = null; - } - } else { - continue; - } - } - // No need to encode an empty section... - if (javaSection.isEmpty()) { + if (javaSection == null || javaSection.isEmpty()) { continue; } diff --git a/connector/src/main/resources/bedrock/blockpalette.1_16_100.nbt b/connector/src/main/resources/bedrock/blockpalette.1_16_100.nbt deleted file mode 100644 index 4513be031b02c9a0b67871b36f14bb136ff30e04..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 104176 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 TUj@e_ci`%xOH5Tr7}oy*%Hl=7 diff --git a/connector/src/main/resources/bedrock/creative_items.json b/connector/src/main/resources/bedrock/creative_items.json index bc5bded52..73172ca53 100644 --- a/connector/src/main/resources/bedrock/creative_items.json +++ b/connector/src/main/resources/bedrock/creative_items.json @@ -2,155 +2,155 @@ "items" : [ { "id" : "minecraft:planks", - "blockRuntimeId" : 4812 + "blockRuntimeId" : 5004 }, { "id" : "minecraft:planks", - "blockRuntimeId" : 4813 + "blockRuntimeId" : 5005 }, { "id" : "minecraft:planks", - "blockRuntimeId" : 4814 + "blockRuntimeId" : 5006 }, { "id" : "minecraft:planks", - "blockRuntimeId" : 4815 + "blockRuntimeId" : 5007 }, { "id" : "minecraft:planks", - "blockRuntimeId" : 4816 + "blockRuntimeId" : 5008 }, { "id" : "minecraft:planks", - "blockRuntimeId" : 4817 + "blockRuntimeId" : 5009 }, { "id" : "minecraft:crimson_planks", - "blockRuntimeId" : 3484 + "blockRuntimeId" : 3605 }, { "id" : "minecraft:warped_planks", - "blockRuntimeId" : 6315 + "blockRuntimeId" : 6529 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 967 + "blockRuntimeId" : 1086 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 968 + "blockRuntimeId" : 1087 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 969 + "blockRuntimeId" : 1088 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 970 + "blockRuntimeId" : 1089 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 971 + "blockRuntimeId" : 1090 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 972 + "blockRuntimeId" : 1091 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 979 + "blockRuntimeId" : 1098 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 974 + "blockRuntimeId" : 1093 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 975 + "blockRuntimeId" : 1094 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 973 + "blockRuntimeId" : 1092 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 976 + "blockRuntimeId" : 1095 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 980 + "blockRuntimeId" : 1099 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 977 + "blockRuntimeId" : 1096 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 978 + "blockRuntimeId" : 1097 }, { "id" : "minecraft:blackstone_wall", - "blockRuntimeId" : 449 + "blockRuntimeId" : 490 }, { "id" : "minecraft:polished_blackstone_wall", - "blockRuntimeId" : 5046 + "blockRuntimeId" : 5248 }, { "id" : "minecraft:polished_blackstone_brick_wall", - "blockRuntimeId" : 4843 + "blockRuntimeId" : 5045 }, { "id" : "minecraft:fence", - "blockRuntimeId" : 4018 + "blockRuntimeId" : 4174 }, { "id" : "minecraft:fence", - "blockRuntimeId" : 4019 + "blockRuntimeId" : 4175 }, { "id" : "minecraft:fence", - "blockRuntimeId" : 4020 + "blockRuntimeId" : 4176 }, { "id" : "minecraft:fence", - "blockRuntimeId" : 4021 + "blockRuntimeId" : 4177 }, { "id" : "minecraft:fence", - "blockRuntimeId" : 4022 + "blockRuntimeId" : 4178 }, { "id" : "minecraft:fence", - "blockRuntimeId" : 4023 + "blockRuntimeId" : 4179 }, { "id" : "minecraft:nether_brick_fence", - "blockRuntimeId" : 4738 + "blockRuntimeId" : 4916 }, { "id" : "minecraft:crimson_fence", - "blockRuntimeId" : 3462 + "blockRuntimeId" : 3583 }, { "id" : "minecraft:warped_fence", - "blockRuntimeId" : 6293 + "blockRuntimeId" : 6507 }, { "id" : "minecraft:fence_gate", - "blockRuntimeId" : 4024 + "blockRuntimeId" : 4180 }, { "id" : "minecraft:spruce_fence_gate", - "blockRuntimeId" : 5729 + "blockRuntimeId" : 5943 }, { "id" : "minecraft:birch_fence_gate", - "blockRuntimeId" : 352 + "blockRuntimeId" : 393 }, { "id" : "minecraft:jungle_fence_gate", - "blockRuntimeId" : 4372 + "blockRuntimeId" : 4542 }, { "id" : "minecraft:acacia_fence_gate", @@ -158,43 +158,43 @@ }, { "id" : "minecraft:dark_oak_fence_gate", - "blockRuntimeId" : 3604 + "blockRuntimeId" : 3736 }, { "id" : "minecraft:crimson_fence_gate", - "blockRuntimeId" : 3463 + "blockRuntimeId" : 3584 }, { "id" : "minecraft:warped_fence_gate", - "blockRuntimeId" : 6294 + "blockRuntimeId" : 6508 }, { "id" : "minecraft:normal_stone_stairs", - "blockRuntimeId" : 4757 + "blockRuntimeId" : 4935 }, { "id" : "minecraft:stone_stairs", - "blockRuntimeId" : 6000 + "blockRuntimeId" : 6214 }, { "id" : "minecraft:mossy_cobblestone_stairs", - "blockRuntimeId" : 4719 + "blockRuntimeId" : 4897 }, { "id" : "minecraft:oak_stairs", - "blockRuntimeId" : 4766 + "blockRuntimeId" : 4944 }, { "id" : "minecraft:spruce_stairs", - "blockRuntimeId" : 5761 + "blockRuntimeId" : 5975 }, { "id" : "minecraft:birch_stairs", - "blockRuntimeId" : 384 + "blockRuntimeId" : 425 }, { "id" : "minecraft:jungle_stairs", - "blockRuntimeId" : 4404 + "blockRuntimeId" : 4574 }, { "id" : "minecraft:acacia_stairs", @@ -202,47 +202,47 @@ }, { "id" : "minecraft:dark_oak_stairs", - "blockRuntimeId" : 3636 + "blockRuntimeId" : 3768 }, { "id" : "minecraft:stone_brick_stairs", - "blockRuntimeId" : 5906 + "blockRuntimeId" : 6120 }, { "id" : "minecraft:mossy_stone_brick_stairs", - "blockRuntimeId" : 4727 + "blockRuntimeId" : 4905 }, { "id" : "minecraft:sandstone_stairs", - "blockRuntimeId" : 5516 + "blockRuntimeId" : 5719 }, { "id" : "minecraft:smooth_sandstone_stairs", - "blockRuntimeId" : 5623 + "blockRuntimeId" : 5836 }, { "id" : "minecraft:red_sandstone_stairs", - "blockRuntimeId" : 5443 + "blockRuntimeId" : 5646 }, { "id" : "minecraft:smooth_red_sandstone_stairs", - "blockRuntimeId" : 5615 + "blockRuntimeId" : 5828 }, { "id" : "minecraft:granite_stairs", - "blockRuntimeId" : 4132 + "blockRuntimeId" : 4301 }, { "id" : "minecraft:polished_granite_stairs", - "blockRuntimeId" : 5216 + "blockRuntimeId" : 5418 }, { "id" : "minecraft:diorite_stairs", - "blockRuntimeId" : 3738 + "blockRuntimeId" : 3870 }, { "id" : "minecraft:polished_diorite_stairs", - "blockRuntimeId" : 5208 + "blockRuntimeId" : 5410 }, { "id" : "minecraft:andesite_stairs", @@ -250,67 +250,95 @@ }, { "id" : "minecraft:polished_andesite_stairs", - "blockRuntimeId" : 4819 + "blockRuntimeId" : 5021 }, { "id" : "minecraft:brick_stairs", - "blockRuntimeId" : 808 + "blockRuntimeId" : 849 }, { "id" : "minecraft:nether_brick_stairs", - "blockRuntimeId" : 4739 + "blockRuntimeId" : 4917 }, { "id" : "minecraft:red_nether_brick_stairs", - "blockRuntimeId" : 5431 + "blockRuntimeId" : 5634 }, { "id" : "minecraft:end_brick_stairs", - "blockRuntimeId" : 3978 + "blockRuntimeId" : 4120 }, { "id" : "minecraft:quartz_stairs", - "blockRuntimeId" : 5378 + "blockRuntimeId" : 5581 }, { "id" : "minecraft:smooth_quartz_stairs", - "blockRuntimeId" : 5607 + "blockRuntimeId" : 5820 }, { "id" : "minecraft:purpur_stairs", - "blockRuntimeId" : 5356 + "blockRuntimeId" : 5559 }, { "id" : "minecraft:prismarine_stairs", - "blockRuntimeId" : 5278 + "blockRuntimeId" : 5481 }, { "id" : "minecraft:dark_prismarine_stairs", - "blockRuntimeId" : 3660 + "blockRuntimeId" : 3792 }, { "id" : "minecraft:prismarine_bricks_stairs", - "blockRuntimeId" : 5270 + "blockRuntimeId" : 5473 }, { "id" : "minecraft:crimson_stairs", - "blockRuntimeId" : 3504 + "blockRuntimeId" : 3625 }, { "id" : "minecraft:warped_stairs", - "blockRuntimeId" : 6335 + "blockRuntimeId" : 6549 }, { "id" : "minecraft:blackstone_stairs", - "blockRuntimeId" : 441 + "blockRuntimeId" : 482 }, { "id" : "minecraft:polished_blackstone_stairs", - "blockRuntimeId" : 5038 + "blockRuntimeId" : 5240 }, { "id" : "minecraft:polished_blackstone_brick_stairs", - "blockRuntimeId" : 4835 + "blockRuntimeId" : 5037 + }, + { + "id" : "minecraft:cut_copper_stairs", + "blockRuntimeId" : 3678 + }, + { + "id" : "minecraft:exposed_cut_copper_stairs", + "blockRuntimeId" : 4156 + }, + { + "id" : "minecraft:weathered_cut_copper_stairs", + "blockRuntimeId" : 6662 + }, + { + "id" : "minecraft:oxidized_cut_copper_stairs", + "blockRuntimeId" : 4975 + }, + { + "id" : "minecraft:waxed_cut_copper_stairs", + "blockRuntimeId" : 6620 + }, + { + "id" : "minecraft:waxed_exposed_cut_copper_stairs", + "blockRuntimeId" : 6634 + }, + { + "id" : "minecraft:waxed_weathered_cut_copper_stairs", + "blockRuntimeId" : 6648 }, { "id" : "minecraft:wooden_door" @@ -341,19 +369,19 @@ }, { "id" : "minecraft:trapdoor", - "blockRuntimeId" : 6081 + "blockRuntimeId" : 6295 }, { "id" : "minecraft:spruce_trapdoor", - "blockRuntimeId" : 5785 + "blockRuntimeId" : 5999 }, { "id" : "minecraft:birch_trapdoor", - "blockRuntimeId" : 408 + "blockRuntimeId" : 449 }, { "id" : "minecraft:jungle_trapdoor", - "blockRuntimeId" : 4428 + "blockRuntimeId" : 4598 }, { "id" : "minecraft:acacia_trapdoor", @@ -361,1243 +389,1339 @@ }, { "id" : "minecraft:dark_oak_trapdoor", - "blockRuntimeId" : 3644 + "blockRuntimeId" : 3776 }, { "id" : "minecraft:iron_trapdoor", - "blockRuntimeId" : 4287 + "blockRuntimeId" : 4457 }, { "id" : "minecraft:crimson_trapdoor", - "blockRuntimeId" : 3531 + "blockRuntimeId" : 3652 }, { "id" : "minecraft:warped_trapdoor", - "blockRuntimeId" : 6362 + "blockRuntimeId" : 6576 }, { "id" : "minecraft:iron_bars", - "blockRuntimeId" : 4252 + "blockRuntimeId" : 4422 }, { "id" : "minecraft:glass", - "blockRuntimeId" : 4114 + "blockRuntimeId" : 4271 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 5807 + "blockRuntimeId" : 6021 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 5815 + "blockRuntimeId" : 6029 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 5814 + "blockRuntimeId" : 6028 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 5822 + "blockRuntimeId" : 6036 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 5819 + "blockRuntimeId" : 6033 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 5821 + "blockRuntimeId" : 6035 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 5808 + "blockRuntimeId" : 6022 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 5811 + "blockRuntimeId" : 6025 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 5812 + "blockRuntimeId" : 6026 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 5820 + "blockRuntimeId" : 6034 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 5816 + "blockRuntimeId" : 6030 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 5810 + "blockRuntimeId" : 6024 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 5818 + "blockRuntimeId" : 6032 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 5817 + "blockRuntimeId" : 6031 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 5809 + "blockRuntimeId" : 6023 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 5813 + "blockRuntimeId" : 6027 }, { "id" : "minecraft:glass_pane", - "blockRuntimeId" : 4115 + "blockRuntimeId" : 4272 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 5823 + "blockRuntimeId" : 6037 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 5831 + "blockRuntimeId" : 6045 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 5830 + "blockRuntimeId" : 6044 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 5838 + "blockRuntimeId" : 6052 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 5835 + "blockRuntimeId" : 6049 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 5837 + "blockRuntimeId" : 6051 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 5824 + "blockRuntimeId" : 6038 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 5827 + "blockRuntimeId" : 6041 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 5828 + "blockRuntimeId" : 6042 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 5836 + "blockRuntimeId" : 6050 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 5832 + "blockRuntimeId" : 6046 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 5826 + "blockRuntimeId" : 6040 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 5834 + "blockRuntimeId" : 6048 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 5833 + "blockRuntimeId" : 6047 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 5825 + "blockRuntimeId" : 6039 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 5829 + "blockRuntimeId" : 6043 }, { "id" : "minecraft:ladder", - "blockRuntimeId" : 4476 + "blockRuntimeId" : 4646 }, { "id" : "minecraft:scaffolding", - "blockRuntimeId" : 5536 + "blockRuntimeId" : 5739 }, { "id" : "minecraft:double_stone_slab", - "blockRuntimeId" : 5942 + "blockRuntimeId" : 6156 }, { "id" : "minecraft:double_stone_slab4", - "blockRuntimeId" : 5992 + "blockRuntimeId" : 6206 }, { "id" : "minecraft:double_stone_slab", - "blockRuntimeId" : 5945 + "blockRuntimeId" : 6159 }, { "id" : "minecraft:double_stone_slab2", - "blockRuntimeId" : 5963 + "blockRuntimeId" : 6177 }, { "id" : "minecraft:wooden_slab", - "blockRuntimeId" : 6540 + "blockRuntimeId" : 6810 }, { "id" : "minecraft:wooden_slab", - "blockRuntimeId" : 6541 + "blockRuntimeId" : 6811 }, { "id" : "minecraft:wooden_slab", - "blockRuntimeId" : 6542 + "blockRuntimeId" : 6812 }, { "id" : "minecraft:wooden_slab", - "blockRuntimeId" : 6543 + "blockRuntimeId" : 6813 }, { "id" : "minecraft:wooden_slab", - "blockRuntimeId" : 6544 + "blockRuntimeId" : 6814 }, { "id" : "minecraft:wooden_slab", - "blockRuntimeId" : 6545 + "blockRuntimeId" : 6815 }, { "id" : "minecraft:double_stone_slab", - "blockRuntimeId" : 5947 + "blockRuntimeId" : 6161 }, { "id" : "minecraft:double_stone_slab4", - "blockRuntimeId" : 5990 + "blockRuntimeId" : 6204 }, { "id" : "minecraft:double_stone_slab", - "blockRuntimeId" : 5943 + "blockRuntimeId" : 6157 }, { "id" : "minecraft:double_stone_slab4", - "blockRuntimeId" : 5993 + "blockRuntimeId" : 6207 }, { "id" : "minecraft:double_stone_slab2", - "blockRuntimeId" : 5964 + "blockRuntimeId" : 6178 }, { "id" : "minecraft:double_stone_slab2", - "blockRuntimeId" : 5958 + "blockRuntimeId" : 6172 }, { "id" : "minecraft:double_stone_slab4", - "blockRuntimeId" : 5994 + "blockRuntimeId" : 6208 }, { "id" : "minecraft:double_stone_slab3", - "blockRuntimeId" : 5975 + "blockRuntimeId" : 6189 }, { "id" : "minecraft:double_stone_slab3", - "blockRuntimeId" : 5980 + "blockRuntimeId" : 6194 }, { "id" : "minecraft:double_stone_slab3", - "blockRuntimeId" : 5981 + "blockRuntimeId" : 6195 }, { "id" : "minecraft:double_stone_slab3", - "blockRuntimeId" : 5978 + "blockRuntimeId" : 6192 }, { "id" : "minecraft:double_stone_slab3", - "blockRuntimeId" : 5979 + "blockRuntimeId" : 6193 }, { "id" : "minecraft:double_stone_slab3", - "blockRuntimeId" : 5977 + "blockRuntimeId" : 6191 }, { "id" : "minecraft:double_stone_slab3", - "blockRuntimeId" : 5976 + "blockRuntimeId" : 6190 }, { "id" : "minecraft:double_stone_slab", - "blockRuntimeId" : 5946 + "blockRuntimeId" : 6160 }, { "id" : "minecraft:double_stone_slab", - "blockRuntimeId" : 5949 + "blockRuntimeId" : 6163 }, { "id" : "minecraft:double_stone_slab2", - "blockRuntimeId" : 5965 + "blockRuntimeId" : 6179 }, { "id" : "minecraft:double_stone_slab3", - "blockRuntimeId" : 5974 + "blockRuntimeId" : 6188 }, { "id" : "minecraft:double_stone_slab", - "blockRuntimeId" : 5948 + "blockRuntimeId" : 6162 }, { "id" : "minecraft:double_stone_slab4", - "blockRuntimeId" : 5991 + "blockRuntimeId" : 6205 }, { "id" : "minecraft:double_stone_slab2", - "blockRuntimeId" : 5959 + "blockRuntimeId" : 6173 }, { "id" : "minecraft:double_stone_slab2", - "blockRuntimeId" : 5960 + "blockRuntimeId" : 6174 }, { "id" : "minecraft:double_stone_slab2", - "blockRuntimeId" : 5961 + "blockRuntimeId" : 6175 }, { "id" : "minecraft:double_stone_slab2", - "blockRuntimeId" : 5962 + "blockRuntimeId" : 6176 }, { "id" : "minecraft:crimson_slab", - "blockRuntimeId" : 3502 + "blockRuntimeId" : 3623 }, { "id" : "minecraft:warped_slab", - "blockRuntimeId" : 6333 + "blockRuntimeId" : 6547 }, { "id" : "minecraft:blackstone_slab", - "blockRuntimeId" : 439 + "blockRuntimeId" : 480 }, { "id" : "minecraft:polished_blackstone_slab", - "blockRuntimeId" : 5036 + "blockRuntimeId" : 5238 }, { "id" : "minecraft:polished_blackstone_brick_slab", - "blockRuntimeId" : 4833 + "blockRuntimeId" : 5035 + }, + { + "id" : "minecraft:cut_copper_slab", + "blockRuntimeId" : 3676 + }, + { + "id" : "minecraft:exposed_cut_copper_slab", + "blockRuntimeId" : 4154 + }, + { + "id" : "minecraft:weathered_cut_copper_slab", + "blockRuntimeId" : 6660 + }, + { + "id" : "minecraft:oxidized_cut_copper_slab", + "blockRuntimeId" : 4973 + }, + { + "id" : "minecraft:waxed_cut_copper_slab", + "blockRuntimeId" : 6618 + }, + { + "id" : "minecraft:waxed_exposed_cut_copper_slab", + "blockRuntimeId" : 6632 + }, + { + "id" : "minecraft:waxed_weathered_cut_copper_slab", + "blockRuntimeId" : 6646 }, { "id" : "minecraft:brick_block", - "blockRuntimeId" : 807 + "blockRuntimeId" : 848 }, { "id" : "minecraft:chiseled_nether_bricks", - "blockRuntimeId" : 954 + "blockRuntimeId" : 1073 }, { "id" : "minecraft:cracked_nether_bricks", - "blockRuntimeId" : 3413 + "blockRuntimeId" : 3534 }, { "id" : "minecraft:quartz_bricks", - "blockRuntimeId" : 5376 + "blockRuntimeId" : 5579 }, { "id" : "minecraft:stonebrick", - "blockRuntimeId" : 6008 + "blockRuntimeId" : 6222 }, { "id" : "minecraft:stonebrick", - "blockRuntimeId" : 6009 + "blockRuntimeId" : 6223 }, { "id" : "minecraft:stonebrick", - "blockRuntimeId" : 6010 + "blockRuntimeId" : 6224 }, { "id" : "minecraft:stonebrick", - "blockRuntimeId" : 6011 + "blockRuntimeId" : 6225 }, { "id" : "minecraft:end_bricks", - "blockRuntimeId" : 3986 + "blockRuntimeId" : 4128 }, { "id" : "minecraft:prismarine", - "blockRuntimeId" : 5269 + "blockRuntimeId" : 5472 }, { "id" : "minecraft:polished_blackstone_bricks", - "blockRuntimeId" : 5005 + "blockRuntimeId" : 5207 }, { "id" : "minecraft:cracked_polished_blackstone_bricks", - "blockRuntimeId" : 3414 + "blockRuntimeId" : 3535 }, { "id" : "minecraft:gilded_blackstone", - "blockRuntimeId" : 4113 + "blockRuntimeId" : 4270 }, { "id" : "minecraft:chiseled_polished_blackstone", - "blockRuntimeId" : 955 + "blockRuntimeId" : 1074 }, { "id" : "minecraft:cobblestone", - "blockRuntimeId" : 966 + "blockRuntimeId" : 1085 }, { "id" : "minecraft:mossy_cobblestone", - "blockRuntimeId" : 4718 + "blockRuntimeId" : 4896 }, { "id" : "minecraft:smooth_stone", - "blockRuntimeId" : 5631 - }, - { - "id" : "minecraft:sandstone", - "blockRuntimeId" : 5512 - }, - { - "id" : "minecraft:sandstone", - "blockRuntimeId" : 5513 - }, - { - "id" : "minecraft:sandstone", - "blockRuntimeId" : 5514 - }, - { - "id" : "minecraft:sandstone", - "blockRuntimeId" : 5515 - }, - { - "id" : "minecraft:red_sandstone", - "blockRuntimeId" : 5439 - }, - { - "id" : "minecraft:red_sandstone", - "blockRuntimeId" : 5440 - }, - { - "id" : "minecraft:red_sandstone", - "blockRuntimeId" : 5441 - }, - { - "id" : "minecraft:red_sandstone", - "blockRuntimeId" : 5442 - }, - { - "id" : "minecraft:coal_block", - "blockRuntimeId" : 964 - }, - { - "id" : "minecraft:dried_kelp_block", - "blockRuntimeId" : 3843 - }, - { - "id" : "minecraft:gold_block", - "blockRuntimeId" : 4118 - }, - { - "id" : "minecraft:iron_block", - "blockRuntimeId" : 4253 - }, - { - "id" : "minecraft:emerald_block", - "blockRuntimeId" : 3975 - }, - { - "id" : "minecraft:diamond_block", - "blockRuntimeId" : 3736 - }, - { - "id" : "minecraft:lapis_block", - "blockRuntimeId" : 4484 - }, - { - "id" : "minecraft:quartz_block", - "blockRuntimeId" : 5364 - }, - { - "id" : "minecraft:quartz_block", - "blockRuntimeId" : 5366 - }, - { - "id" : "minecraft:quartz_block", - "blockRuntimeId" : 5365 - }, - { - "id" : "minecraft:quartz_block", - "blockRuntimeId" : 5367 - }, - { - "id" : "minecraft:prismarine", - "blockRuntimeId" : 5267 - }, - { - "id" : "minecraft:prismarine", - "blockRuntimeId" : 5268 - }, - { - "id" : "minecraft:slime", - "blockRuntimeId" : 5599 - }, - { - "id" : "minecraft:honey_block", - "blockRuntimeId" : 4234 - }, - { - "id" : "minecraft:honeycomb_block", - "blockRuntimeId" : 4235 - }, - { - "id" : "minecraft:hay_block", - "blockRuntimeId" : 4206 - }, - { - "id" : "minecraft:bone_block", - "blockRuntimeId" : 624 - }, - { - "id" : "minecraft:nether_brick", - "blockRuntimeId" : 4737 - }, - { - "id" : "minecraft:red_nether_brick", - "blockRuntimeId" : 5430 - }, - { - "id" : "minecraft:netherite_block", - "blockRuntimeId" : 4754 - }, - { - "id" : "minecraft:lodestone", - "blockRuntimeId" : 4632 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 6552 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 6560 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 6559 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 6567 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 6564 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 6566 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 6553 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 6556 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 6557 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 6565 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 6561 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 6555 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 6563 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 6562 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 6554 - }, - { - "id" : "minecraft:wool", - "blockRuntimeId" : 6558 - }, - { - "id" : "minecraft:carpet", - "blockRuntimeId" : 873 - }, - { - "id" : "minecraft:carpet", - "blockRuntimeId" : 881 - }, - { - "id" : "minecraft:carpet", - "blockRuntimeId" : 880 - }, - { - "id" : "minecraft:carpet", - "blockRuntimeId" : 888 - }, - { - "id" : "minecraft:carpet", - "blockRuntimeId" : 885 - }, - { - "id" : "minecraft:carpet", - "blockRuntimeId" : 887 - }, - { - "id" : "minecraft:carpet", - "blockRuntimeId" : 874 - }, - { - "id" : "minecraft:carpet", - "blockRuntimeId" : 877 - }, - { - "id" : "minecraft:carpet", - "blockRuntimeId" : 878 - }, - { - "id" : "minecraft:carpet", - "blockRuntimeId" : 886 - }, - { - "id" : "minecraft:carpet", - "blockRuntimeId" : 882 - }, - { - "id" : "minecraft:carpet", - "blockRuntimeId" : 876 - }, - { - "id" : "minecraft:carpet", - "blockRuntimeId" : 884 - }, - { - "id" : "minecraft:carpet", - "blockRuntimeId" : 883 - }, - { - "id" : "minecraft:carpet", - "blockRuntimeId" : 875 - }, - { - "id" : "minecraft:carpet", - "blockRuntimeId" : 879 - }, - { - "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3308 - }, - { - "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3316 - }, - { - "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3315 - }, - { - "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3323 - }, - { - "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3320 - }, - { - "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3322 - }, - { - "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3309 - }, - { - "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3312 - }, - { - "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3313 - }, - { - "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3321 - }, - { - "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3317 - }, - { - "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3311 - }, - { - "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3319 - }, - { - "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3318 - }, - { - "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3310 - }, - { - "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3314 - }, - { - "id" : "minecraft:concrete", - "blockRuntimeId" : 3292 - }, - { - "id" : "minecraft:concrete", - "blockRuntimeId" : 3300 - }, - { - "id" : "minecraft:concrete", - "blockRuntimeId" : 3299 - }, - { - "id" : "minecraft:concrete", - "blockRuntimeId" : 3307 - }, - { - "id" : "minecraft:concrete", - "blockRuntimeId" : 3304 - }, - { - "id" : "minecraft:concrete", - "blockRuntimeId" : 3306 - }, - { - "id" : "minecraft:concrete", - "blockRuntimeId" : 3293 - }, - { - "id" : "minecraft:concrete", - "blockRuntimeId" : 3296 - }, - { - "id" : "minecraft:concrete", - "blockRuntimeId" : 3297 - }, - { - "id" : "minecraft:concrete", - "blockRuntimeId" : 3305 - }, - { - "id" : "minecraft:concrete", - "blockRuntimeId" : 3301 - }, - { - "id" : "minecraft:concrete", - "blockRuntimeId" : 3295 - }, - { - "id" : "minecraft:concrete", - "blockRuntimeId" : 3303 - }, - { - "id" : "minecraft:concrete", - "blockRuntimeId" : 3302 - }, - { - "id" : "minecraft:concrete", - "blockRuntimeId" : 3294 - }, - { - "id" : "minecraft:concrete", - "blockRuntimeId" : 3298 - }, - { - "id" : "minecraft:clay", - "blockRuntimeId" : 963 - }, - { - "id" : "minecraft:hardened_clay", - "blockRuntimeId" : 4205 - }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 5839 - }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 5847 - }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 5846 - }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 5854 - }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 5851 - }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 5853 - }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 5840 - }, - { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 5843 - }, - { - "id" : "minecraft:stained_hardened_clay", "blockRuntimeId" : 5844 }, { - "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 5852 + "id" : "minecraft:sandstone", + "blockRuntimeId" : 5715 + }, + { + "id" : "minecraft:sandstone", + "blockRuntimeId" : 5716 + }, + { + "id" : "minecraft:sandstone", + "blockRuntimeId" : 5717 + }, + { + "id" : "minecraft:sandstone", + "blockRuntimeId" : 5718 + }, + { + "id" : "minecraft:red_sandstone", + "blockRuntimeId" : 5642 + }, + { + "id" : "minecraft:red_sandstone", + "blockRuntimeId" : 5643 + }, + { + "id" : "minecraft:red_sandstone", + "blockRuntimeId" : 5644 + }, + { + "id" : "minecraft:red_sandstone", + "blockRuntimeId" : 5645 + }, + { + "id" : "minecraft:coal_block", + "blockRuntimeId" : 1083 + }, + { + "id" : "minecraft:dried_kelp_block", + "blockRuntimeId" : 3978 + }, + { + "id" : "minecraft:gold_block", + "blockRuntimeId" : 4287 + }, + { + "id" : "minecraft:iron_block", + "blockRuntimeId" : 4423 + }, + { + "id" : "minecraft:copper_block", + "blockRuntimeId" : 3444 + }, + { + "id" : "minecraft:exposed_copper", + "blockRuntimeId" : 4152 + }, + { + "id" : "minecraft:weathered_copper", + "blockRuntimeId" : 6658 + }, + { + "id" : "minecraft:oxidized_copper", + "blockRuntimeId" : 4971 + }, + { + "id" : "minecraft:waxed_copper", + "blockRuntimeId" : 6616 + }, + { + "id" : "minecraft:waxed_exposed_copper", + "blockRuntimeId" : 6630 + }, + { + "id" : "minecraft:waxed_weathered_copper", + "blockRuntimeId" : 6644 + }, + { + "id" : "minecraft:cut_copper", + "blockRuntimeId" : 3675 + }, + { + "id" : "minecraft:exposed_cut_copper", + "blockRuntimeId" : 4153 + }, + { + "id" : "minecraft:weathered_cut_copper", + "blockRuntimeId" : 6659 + }, + { + "id" : "minecraft:oxidized_cut_copper", + "blockRuntimeId" : 4972 + }, + { + "id" : "minecraft:waxed_cut_copper", + "blockRuntimeId" : 6617 + }, + { + "id" : "minecraft:waxed_exposed_cut_copper", + "blockRuntimeId" : 6631 + }, + { + "id" : "minecraft:waxed_weathered_cut_copper", + "blockRuntimeId" : 6645 + }, + { + "id" : "minecraft:emerald_block", + "blockRuntimeId" : 4117 + }, + { + "id" : "minecraft:diamond_block", + "blockRuntimeId" : 3868 + }, + { + "id" : "minecraft:lapis_block", + "blockRuntimeId" : 4654 + }, + { + "id" : "minecraft:quartz_block", + "blockRuntimeId" : 5567 + }, + { + "id" : "minecraft:quartz_block", + "blockRuntimeId" : 5569 + }, + { + "id" : "minecraft:quartz_block", + "blockRuntimeId" : 5568 + }, + { + "id" : "minecraft:quartz_block", + "blockRuntimeId" : 5570 + }, + { + "id" : "minecraft:prismarine", + "blockRuntimeId" : 5470 + }, + { + "id" : "minecraft:prismarine", + "blockRuntimeId" : 5471 + }, + { + "id" : "minecraft:slime", + "blockRuntimeId" : 5804 + }, + { + "id" : "minecraft:honey_block", + "blockRuntimeId" : 4404 + }, + { + "id" : "minecraft:honeycomb_block", + "blockRuntimeId" : 4405 + }, + { + "id" : "minecraft:hay_block", + "blockRuntimeId" : 4376 + }, + { + "id" : "minecraft:bone_block", + "blockRuntimeId" : 665 + }, + { + "id" : "minecraft:nether_brick", + "blockRuntimeId" : 4915 + }, + { + "id" : "minecraft:red_nether_brick", + "blockRuntimeId" : 5633 + }, + { + "id" : "minecraft:netherite_block", + "blockRuntimeId" : 4932 + }, + { + "id" : "minecraft:lodestone", + "blockRuntimeId" : 4808 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 6822 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 6830 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 6829 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 6837 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 6834 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 6836 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 6823 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 6826 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 6827 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 6835 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 6831 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 6825 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 6833 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 6832 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 6824 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 6828 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 914 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 922 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 921 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 929 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 926 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 928 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 915 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 918 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 919 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 927 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 923 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 917 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 925 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 924 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 916 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 920 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 3427 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 3435 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 3434 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 3442 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 3439 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 3441 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 3428 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 3431 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 3432 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 3440 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 3436 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 3430 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 3438 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 3437 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 3429 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 3433 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 3411 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 3419 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 3418 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 3426 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 3423 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 3425 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 3412 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 3415 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 3416 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 3424 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 3420 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 3414 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 3422 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 3421 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 3413 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 3417 + }, + { + "id" : "minecraft:clay", + "blockRuntimeId" : 1082 + }, + { + "id" : "minecraft:hardened_clay", + "blockRuntimeId" : 4375 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 5848 + "blockRuntimeId" : 6053 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 5842 + "blockRuntimeId" : 6061 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 5850 + "blockRuntimeId" : 6060 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 5849 + "blockRuntimeId" : 6068 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 5841 + "blockRuntimeId" : 6065 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 5845 + "blockRuntimeId" : 6067 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 6054 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 6057 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 6058 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 6066 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 6062 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 6056 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 6064 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 6063 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 6055 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 6059 }, { "id" : "minecraft:white_glazed_terracotta", - "blockRuntimeId" : 6437 + "blockRuntimeId" : 6707 }, { "id" : "minecraft:silver_glazed_terracotta", - "blockRuntimeId" : 5581 + "blockRuntimeId" : 5786 }, { "id" : "minecraft:gray_glazed_terracotta", - "blockRuntimeId" : 4143 + "blockRuntimeId" : 4312 }, { "id" : "minecraft:black_glazed_terracotta", - "blockRuntimeId" : 430 + "blockRuntimeId" : 471 }, { "id" : "minecraft:brown_glazed_terracotta", - "blockRuntimeId" : 816 + "blockRuntimeId" : 857 }, { "id" : "minecraft:red_glazed_terracotta", - "blockRuntimeId" : 5407 + "blockRuntimeId" : 5610 }, { "id" : "minecraft:orange_glazed_terracotta", - "blockRuntimeId" : 4787 + "blockRuntimeId" : 4965 }, { "id" : "minecraft:yellow_glazed_terracotta", - "blockRuntimeId" : 6569 + "blockRuntimeId" : 6839 }, { "id" : "minecraft:lime_glazed_terracotta", - "blockRuntimeId" : 4602 + "blockRuntimeId" : 4778 }, { "id" : "minecraft:green_glazed_terracotta", - "blockRuntimeId" : 4149 + "blockRuntimeId" : 4318 }, { "id" : "minecraft:cyan_glazed_terracotta", - "blockRuntimeId" : 3554 + "blockRuntimeId" : 3686 }, { "id" : "minecraft:light_blue_glazed_terracotta", - "blockRuntimeId" : 4580 + "blockRuntimeId" : 4750 }, { "id" : "minecraft:blue_glazed_terracotta", - "blockRuntimeId" : 617 + "blockRuntimeId" : 658 }, { "id" : "minecraft:purple_glazed_terracotta", - "blockRuntimeId" : 5338 + "blockRuntimeId" : 5541 }, { "id" : "minecraft:magenta_glazed_terracotta", - "blockRuntimeId" : 4655 + "blockRuntimeId" : 4831 }, { "id" : "minecraft:pink_glazed_terracotta", - "blockRuntimeId" : 4794 + "blockRuntimeId" : 4986 }, { "id" : "minecraft:purpur_block", - "blockRuntimeId" : 5344 + "blockRuntimeId" : 5547 }, { "id" : "minecraft:purpur_block", - "blockRuntimeId" : 5346 + "blockRuntimeId" : 5549 }, { "id" : "minecraft:nether_wart_block", - "blockRuntimeId" : 4753 + "blockRuntimeId" : 4931 }, { "id" : "minecraft:warped_wart_block", - "blockRuntimeId" : 6384 + "blockRuntimeId" : 6598 }, { "id" : "minecraft:shroomlight", - "blockRuntimeId" : 5564 + "blockRuntimeId" : 5769 }, { "id" : "minecraft:crimson_nylium", - "blockRuntimeId" : 3483 + "blockRuntimeId" : 3604 }, { "id" : "minecraft:warped_nylium", - "blockRuntimeId" : 6314 + "blockRuntimeId" : 6528 }, { "id" : "minecraft:basalt", - "blockRuntimeId" : 198 + "blockRuntimeId" : 207 }, { "id" : "minecraft:polished_basalt", - "blockRuntimeId" : 4827 + "blockRuntimeId" : 5029 }, { "id" : "minecraft:soul_soil", - "blockRuntimeId" : 5676 + "blockRuntimeId" : 5889 }, { "id" : "minecraft:dirt", - "blockRuntimeId" : 3746 + "blockRuntimeId" : 3878 }, { "id" : "minecraft:dirt", - "blockRuntimeId" : 3747 + "blockRuntimeId" : 3879 }, { "id" : "minecraft:farmland", - "blockRuntimeId" : 4010 + "blockRuntimeId" : 4166 }, { "id" : "minecraft:grass", - "blockRuntimeId" : 4140 + "blockRuntimeId" : 4309 }, { "id" : "minecraft:grass_path", - "blockRuntimeId" : 4141 + "blockRuntimeId" : 4310 }, { "id" : "minecraft:podzol", - "blockRuntimeId" : 4818 + "blockRuntimeId" : 5010 }, { "id" : "minecraft:mycelium", - "blockRuntimeId" : 4736 + "blockRuntimeId" : 4914 }, { "id" : "minecraft:stone", - "blockRuntimeId" : 5899 + "blockRuntimeId" : 6113 }, { "id" : "minecraft:iron_ore", - "blockRuntimeId" : 4286 + "blockRuntimeId" : 4456 }, { "id" : "minecraft:gold_ore", - "blockRuntimeId" : 4119 + "blockRuntimeId" : 4288 }, { "id" : "minecraft:diamond_ore", - "blockRuntimeId" : 3737 + "blockRuntimeId" : 3869 }, { "id" : "minecraft:lapis_ore", - "blockRuntimeId" : 4485 + "blockRuntimeId" : 4655 }, { "id" : "minecraft:redstone_ore", - "blockRuntimeId" : 5453 + "blockRuntimeId" : 5656 }, { "id" : "minecraft:coal_ore", - "blockRuntimeId" : 965 + "blockRuntimeId" : 1084 }, { "id" : "minecraft:emerald_ore", - "blockRuntimeId" : 3976 + "blockRuntimeId" : 4118 }, { "id" : "minecraft:quartz_ore", - "blockRuntimeId" : 5377 + "blockRuntimeId" : 5580 }, { "id" : "minecraft:nether_gold_ore", - "blockRuntimeId" : 4747 + "blockRuntimeId" : 4925 }, { "id" : "minecraft:ancient_debris", "blockRuntimeId" : 136 }, + { + "id" : "minecraft:copper_ore", + "blockRuntimeId" : 3445 + }, { "id" : "minecraft:gravel", - "blockRuntimeId" : 4142 + "blockRuntimeId" : 4311 }, { "id" : "minecraft:stone", - "blockRuntimeId" : 5900 + "blockRuntimeId" : 6114 }, { "id" : "minecraft:stone", - "blockRuntimeId" : 5902 + "blockRuntimeId" : 6116 }, { "id" : "minecraft:stone", - "blockRuntimeId" : 5904 + "blockRuntimeId" : 6118 }, { "id" : "minecraft:blackstone", - "blockRuntimeId" : 436 + "blockRuntimeId" : 477 }, { "id" : "minecraft:stone", - "blockRuntimeId" : 5901 + "blockRuntimeId" : 6115 }, { "id" : "minecraft:stone", - "blockRuntimeId" : 5903 + "blockRuntimeId" : 6117 }, { "id" : "minecraft:stone", - "blockRuntimeId" : 5905 + "blockRuntimeId" : 6119 }, { "id" : "minecraft:polished_blackstone", - "blockRuntimeId" : 4830 + "blockRuntimeId" : 5032 }, { "id" : "minecraft:sand", - "blockRuntimeId" : 5510 + "blockRuntimeId" : 5713 }, { "id" : "minecraft:sand", - "blockRuntimeId" : 5511 + "blockRuntimeId" : 5714 }, { "id" : "minecraft:cactus", - "blockRuntimeId" : 841 + "blockRuntimeId" : 882 }, { "id" : "minecraft:log", - "blockRuntimeId" : 4633 + "blockRuntimeId" : 4809 }, { "id" : "minecraft:stripped_oak_log", - "blockRuntimeId" : 6038 + "blockRuntimeId" : 6252 }, { "id" : "minecraft:log", - "blockRuntimeId" : 4634 + "blockRuntimeId" : 4810 }, { "id" : "minecraft:stripped_spruce_log", - "blockRuntimeId" : 6041 + "blockRuntimeId" : 6255 }, { "id" : "minecraft:log", - "blockRuntimeId" : 4635 + "blockRuntimeId" : 4811 }, { "id" : "minecraft:stripped_birch_log", - "blockRuntimeId" : 6023 + "blockRuntimeId" : 6237 }, { "id" : "minecraft:log", - "blockRuntimeId" : 4636 + "blockRuntimeId" : 4812 }, { "id" : "minecraft:stripped_jungle_log", - "blockRuntimeId" : 6035 + "blockRuntimeId" : 6249 }, { "id" : "minecraft:log2", - "blockRuntimeId" : 4645 + "blockRuntimeId" : 4821 }, { "id" : "minecraft:stripped_acacia_log", - "blockRuntimeId" : 6020 + "blockRuntimeId" : 6234 }, { "id" : "minecraft:log2", - "blockRuntimeId" : 4646 + "blockRuntimeId" : 4822 }, { "id" : "minecraft:stripped_dark_oak_log", - "blockRuntimeId" : 6032 + "blockRuntimeId" : 6246 }, { "id" : "minecraft:crimson_stem", - "blockRuntimeId" : 3528 + "blockRuntimeId" : 3649 }, { "id" : "minecraft:stripped_crimson_stem", - "blockRuntimeId" : 6029 + "blockRuntimeId" : 6243 }, { "id" : "minecraft:warped_stem", - "blockRuntimeId" : 6359 + "blockRuntimeId" : 6573 }, { "id" : "minecraft:stripped_warped_stem", - "blockRuntimeId" : 6047 + "blockRuntimeId" : 6261 }, { "id" : "minecraft:wood", - "blockRuntimeId" : 6444 + "blockRuntimeId" : 6714 }, { "id" : "minecraft:wood", - "blockRuntimeId" : 6450 + "blockRuntimeId" : 6720 }, { "id" : "minecraft:wood", - "blockRuntimeId" : 6445 + "blockRuntimeId" : 6715 }, { "id" : "minecraft:wood", - "blockRuntimeId" : 6451 + "blockRuntimeId" : 6721 }, { "id" : "minecraft:wood", - "blockRuntimeId" : 6446 + "blockRuntimeId" : 6716 }, { "id" : "minecraft:wood", - "blockRuntimeId" : 6452 + "blockRuntimeId" : 6722 }, { "id" : "minecraft:wood", - "blockRuntimeId" : 6447 + "blockRuntimeId" : 6717 }, { "id" : "minecraft:wood", - "blockRuntimeId" : 6453 + "blockRuntimeId" : 6723 }, { "id" : "minecraft:wood", - "blockRuntimeId" : 6448 + "blockRuntimeId" : 6718 }, { "id" : "minecraft:wood", - "blockRuntimeId" : 6454 + "blockRuntimeId" : 6724 }, { "id" : "minecraft:wood", - "blockRuntimeId" : 6449 + "blockRuntimeId" : 6719 }, { "id" : "minecraft:wood", - "blockRuntimeId" : 6455 + "blockRuntimeId" : 6725 }, { "id" : "minecraft:crimson_hyphae", - "blockRuntimeId" : 3480 + "blockRuntimeId" : 3601 }, { "id" : "minecraft:stripped_crimson_hyphae", - "blockRuntimeId" : 6026 + "blockRuntimeId" : 6240 }, { "id" : "minecraft:warped_hyphae", - "blockRuntimeId" : 6311 + "blockRuntimeId" : 6525 }, { "id" : "minecraft:stripped_warped_hyphae", - "blockRuntimeId" : 6044 + "blockRuntimeId" : 6258 }, { "id" : "minecraft:leaves", - "blockRuntimeId" : 4516 + "blockRuntimeId" : 4686 }, { "id" : "minecraft:leaves", - "blockRuntimeId" : 4517 + "blockRuntimeId" : 4687 }, { "id" : "minecraft:leaves", - "blockRuntimeId" : 4518 + "blockRuntimeId" : 4688 }, { "id" : "minecraft:leaves", - "blockRuntimeId" : 4519 + "blockRuntimeId" : 4689 }, { "id" : "minecraft:leaves2", - "blockRuntimeId" : 4532 + "blockRuntimeId" : 4702 }, { "id" : "minecraft:leaves2", - "blockRuntimeId" : 4533 + "blockRuntimeId" : 4703 + }, + { + "id" : "minecraft:azalea_leaves", + "blockRuntimeId" : 162 + }, + { + "id" : "minecraft:azalea_leaves_flowered", + "blockRuntimeId" : 166 }, { "id" : "minecraft:sapling", - "blockRuntimeId" : 5524 + "blockRuntimeId" : 5727 }, { "id" : "minecraft:sapling", - "blockRuntimeId" : 5525 + "blockRuntimeId" : 5728 }, { "id" : "minecraft:sapling", - "blockRuntimeId" : 5526 + "blockRuntimeId" : 5729 }, { "id" : "minecraft:sapling", - "blockRuntimeId" : 5527 + "blockRuntimeId" : 5730 }, { "id" : "minecraft:sapling", - "blockRuntimeId" : 5528 + "blockRuntimeId" : 5731 }, { "id" : "minecraft:sapling", - "blockRuntimeId" : 5529 + "blockRuntimeId" : 5732 }, { "id" : "minecraft:bee_nest", - "blockRuntimeId" : 220 + "blockRuntimeId" : 229 }, { "id" : "minecraft:wheat_seeds" @@ -1640,7 +1764,7 @@ }, { "id" : "minecraft:melon_block", - "blockRuntimeId" : 4662 + "blockRuntimeId" : 4838 }, { "id" : "minecraft:melon_slice" @@ -1651,202 +1775,205 @@ { "id" : "minecraft:sweet_berries" }, + { + "id" : "minecraft:glow_berries" + }, { "id" : "minecraft:pumpkin", - "blockRuntimeId" : 5286 + "blockRuntimeId" : 5489 }, { "id" : "minecraft:carved_pumpkin", - "blockRuntimeId" : 898 + "blockRuntimeId" : 939 }, { "id" : "minecraft:lit_pumpkin", - "blockRuntimeId" : 4620 + "blockRuntimeId" : 4796 }, { "id" : "minecraft:honeycomb" }, { "id" : "minecraft:tallgrass", - "blockRuntimeId" : 6068 + "blockRuntimeId" : 6282 }, { "id" : "minecraft:double_plant", - "blockRuntimeId" : 3763 + "blockRuntimeId" : 3898 }, { "id" : "minecraft:tallgrass", - "blockRuntimeId" : 6067 + "blockRuntimeId" : 6281 }, { "id" : "minecraft:double_plant", - "blockRuntimeId" : 3762 + "blockRuntimeId" : 3897 }, { "id" : "minecraft:nether_sprouts" }, { "id" : "minecraft:coral", - "blockRuntimeId" : 3328 + "blockRuntimeId" : 3449 }, { "id" : "minecraft:coral", - "blockRuntimeId" : 3326 + "blockRuntimeId" : 3447 }, { "id" : "minecraft:coral", - "blockRuntimeId" : 3327 + "blockRuntimeId" : 3448 }, { "id" : "minecraft:coral", - "blockRuntimeId" : 3325 + "blockRuntimeId" : 3446 }, { "id" : "minecraft:coral", - "blockRuntimeId" : 3329 + "blockRuntimeId" : 3450 }, { "id" : "minecraft:coral", - "blockRuntimeId" : 3333 + "blockRuntimeId" : 3454 }, { "id" : "minecraft:coral", - "blockRuntimeId" : 3331 + "blockRuntimeId" : 3452 }, { "id" : "minecraft:coral", - "blockRuntimeId" : 3332 + "blockRuntimeId" : 3453 }, { "id" : "minecraft:coral", - "blockRuntimeId" : 3330 + "blockRuntimeId" : 3451 }, { "id" : "minecraft:coral", - "blockRuntimeId" : 3334 + "blockRuntimeId" : 3455 }, { "id" : "minecraft:coral_fan", - "blockRuntimeId" : 3348 + "blockRuntimeId" : 3469 }, { "id" : "minecraft:coral_fan", - "blockRuntimeId" : 3346 + "blockRuntimeId" : 3467 }, { "id" : "minecraft:coral_fan", - "blockRuntimeId" : 3347 + "blockRuntimeId" : 3468 }, { "id" : "minecraft:coral_fan", - "blockRuntimeId" : 3345 + "blockRuntimeId" : 3466 }, { "id" : "minecraft:coral_fan", - "blockRuntimeId" : 3349 + "blockRuntimeId" : 3470 }, { "id" : "minecraft:coral_fan_dead", - "blockRuntimeId" : 3358 + "blockRuntimeId" : 3479 }, { "id" : "minecraft:coral_fan_dead", - "blockRuntimeId" : 3356 + "blockRuntimeId" : 3477 }, { "id" : "minecraft:coral_fan_dead", - "blockRuntimeId" : 3357 + "blockRuntimeId" : 3478 }, { "id" : "minecraft:coral_fan_dead", - "blockRuntimeId" : 3355 + "blockRuntimeId" : 3476 }, { "id" : "minecraft:coral_fan_dead", - "blockRuntimeId" : 3359 + "blockRuntimeId" : 3480 }, { "id" : "minecraft:kelp" }, { "id" : "minecraft:seagrass", - "blockRuntimeId" : 5560 + "blockRuntimeId" : 5765 }, { "id" : "minecraft:crimson_roots", - "blockRuntimeId" : 3501 + "blockRuntimeId" : 3622 }, { "id" : "minecraft:warped_roots", - "blockRuntimeId" : 6332 + "blockRuntimeId" : 6546 }, { "id" : "minecraft:yellow_flower", - "blockRuntimeId" : 6568 + "blockRuntimeId" : 6838 }, { "id" : "minecraft:red_flower", - "blockRuntimeId" : 5396 + "blockRuntimeId" : 5599 }, { "id" : "minecraft:red_flower", - "blockRuntimeId" : 5397 + "blockRuntimeId" : 5600 }, { "id" : "minecraft:red_flower", - "blockRuntimeId" : 5398 + "blockRuntimeId" : 5601 }, { "id" : "minecraft:red_flower", - "blockRuntimeId" : 5399 + "blockRuntimeId" : 5602 }, { "id" : "minecraft:red_flower", - "blockRuntimeId" : 5400 + "blockRuntimeId" : 5603 }, { "id" : "minecraft:red_flower", - "blockRuntimeId" : 5401 + "blockRuntimeId" : 5604 }, { "id" : "minecraft:red_flower", - "blockRuntimeId" : 5402 + "blockRuntimeId" : 5605 }, { "id" : "minecraft:red_flower", - "blockRuntimeId" : 5403 + "blockRuntimeId" : 5606 }, { "id" : "minecraft:red_flower", - "blockRuntimeId" : 5404 + "blockRuntimeId" : 5607 }, { "id" : "minecraft:red_flower", - "blockRuntimeId" : 5405 + "blockRuntimeId" : 5608 }, { "id" : "minecraft:red_flower", - "blockRuntimeId" : 5406 + "blockRuntimeId" : 5609 }, { "id" : "minecraft:double_plant", - "blockRuntimeId" : 3760 + "blockRuntimeId" : 3895 }, { "id" : "minecraft:double_plant", - "blockRuntimeId" : 3761 + "blockRuntimeId" : 3896 }, { "id" : "minecraft:double_plant", - "blockRuntimeId" : 3764 + "blockRuntimeId" : 3899 }, { "id" : "minecraft:double_plant", - "blockRuntimeId" : 3765 + "blockRuntimeId" : 3900 }, { "id" : "minecraft:wither_rose", - "blockRuntimeId" : 6443 + "blockRuntimeId" : 6713 }, { "id" : "minecraft:white_dye" @@ -1899,6 +2026,9 @@ { "id" : "minecraft:ink_sac" }, + { + "id" : "minecraft:glow_ink_sac" + }, { "id" : "minecraft:cocoa_beans" }, @@ -1910,47 +2040,95 @@ }, { "id" : "minecraft:vine", - "blockRuntimeId" : 6219 + "blockRuntimeId" : 6433 }, { "id" : "minecraft:weeping_vines", - "blockRuntimeId" : 6403 + "blockRuntimeId" : 6673 }, { "id" : "minecraft:twisting_vines", - "blockRuntimeId" : 6147 + "blockRuntimeId" : 6361 }, { "id" : "minecraft:waterlily", - "blockRuntimeId" : 6401 + "blockRuntimeId" : 6615 }, { "id" : "minecraft:deadbush", - "blockRuntimeId" : 3722 + "blockRuntimeId" : 3854 }, { "id" : "minecraft:bamboo", - "blockRuntimeId" : 161 + "blockRuntimeId" : 170 }, { "id" : "minecraft:snow", - "blockRuntimeId" : 5632 + "blockRuntimeId" : 5845 }, { "id" : "minecraft:ice", - "blockRuntimeId" : 4248 + "blockRuntimeId" : 4418 }, { "id" : "minecraft:packed_ice", - "blockRuntimeId" : 4793 + "blockRuntimeId" : 4985 }, { "id" : "minecraft:blue_ice", - "blockRuntimeId" : 623 + "blockRuntimeId" : 664 }, { "id" : "minecraft:snow_layer", - "blockRuntimeId" : 5633 + "blockRuntimeId" : 5846 + }, + { + "id" : "minecraft:pointed_dripstone", + "blockRuntimeId" : 5016 + }, + { + "id" : "minecraft:sculk_sensor", + "blockRuntimeId" : 5755 + }, + { + "id" : "minecraft:dripstone_block", + "blockRuntimeId" : 3979 + }, + { + "id" : "minecraft:moss_carpet", + "blockRuntimeId" : 4895 + }, + { + "id" : "minecraft:moss_block", + "blockRuntimeId" : 4894 + }, + { + "id" : "minecraft:dirt_with_roots", + "blockRuntimeId" : 3880 + }, + { + "id" : "minecraft:hanging_roots", + "blockRuntimeId" : 4340 + }, + { + "id" : "minecraft:big_dripleaf", + "blockRuntimeId" : 321 + }, + { + "id" : "minecraft:small_dripleaf_block", + "blockRuntimeId" : 5812 + }, + { + "id" : "minecraft:spore_blossom", + "blockRuntimeId" : 5898 + }, + { + "id" : "minecraft:azalea", + "blockRuntimeId" : 161 + }, + { + "id" : "minecraft:flowering_azalea", + "blockRuntimeId" : 4215 }, { "id" : "minecraft:chicken" @@ -1981,35 +2159,35 @@ }, { "id" : "minecraft:brown_mushroom", - "blockRuntimeId" : 822 + "blockRuntimeId" : 863 }, { "id" : "minecraft:red_mushroom", - "blockRuntimeId" : 5413 + "blockRuntimeId" : 5616 }, { "id" : "minecraft:crimson_fungus", - "blockRuntimeId" : 3479 + "blockRuntimeId" : 3600 }, { "id" : "minecraft:warped_fungus", - "blockRuntimeId" : 6310 + "blockRuntimeId" : 6524 }, { "id" : "minecraft:brown_mushroom_block", - "blockRuntimeId" : 837 + "blockRuntimeId" : 878 }, { "id" : "minecraft:red_mushroom_block", - "blockRuntimeId" : 5428 + "blockRuntimeId" : 5631 }, { "id" : "minecraft:brown_mushroom_block", - "blockRuntimeId" : 838 + "blockRuntimeId" : 879 }, { "id" : "minecraft:brown_mushroom_block", - "blockRuntimeId" : 823 + "blockRuntimeId" : 864 }, { "id" : "minecraft:egg" @@ -2028,46 +2206,46 @@ }, { "id" : "minecraft:web", - "blockRuntimeId" : 6402 + "blockRuntimeId" : 6672 }, { "id" : "minecraft:spider_eye" }, { "id" : "minecraft:mob_spawner", - "blockRuntimeId" : 4711 + "blockRuntimeId" : 4887 }, { "id" : "minecraft:monster_egg", - "blockRuntimeId" : 4712 + "blockRuntimeId" : 4888 }, { "id" : "minecraft:monster_egg", - "blockRuntimeId" : 4713 + "blockRuntimeId" : 4889 }, { "id" : "minecraft:monster_egg", - "blockRuntimeId" : 4714 + "blockRuntimeId" : 4890 }, { "id" : "minecraft:monster_egg", - "blockRuntimeId" : 4715 + "blockRuntimeId" : 4891 }, { "id" : "minecraft:monster_egg", - "blockRuntimeId" : 4716 + "blockRuntimeId" : 4892 }, { "id" : "minecraft:monster_egg", - "blockRuntimeId" : 4717 + "blockRuntimeId" : 4893 }, { "id" : "minecraft:dragon_egg", - "blockRuntimeId" : 3842 + "blockRuntimeId" : 3977 }, { "id" : "minecraft:turtle_egg", - "blockRuntimeId" : 6135 + "blockRuntimeId" : 6349 }, { "id" : "minecraft:chicken_spawn_egg" @@ -2189,6 +2367,9 @@ { "id" : "minecraft:squid_spawn_egg" }, + { + "id" : "minecraft:glow_squid_spawn_egg" + }, { "id" : "minecraft:cave_spider_spawn_egg" }, @@ -2222,6 +2403,9 @@ { "id" : "minecraft:piglin_brute_spawn_egg" }, + { + "id" : "minecraft:goat_spawn_egg" + }, { "id" : "minecraft:ghast_spawn_egg" }, @@ -2260,42 +2444,42 @@ }, { "id" : "minecraft:obsidian", - "blockRuntimeId" : 4786 + "blockRuntimeId" : 4964 }, { "id" : "minecraft:crying_obsidian", - "blockRuntimeId" : 3553 + "blockRuntimeId" : 3674 }, { "id" : "minecraft:bedrock", - "blockRuntimeId" : 218 + "blockRuntimeId" : 227 }, { "id" : "minecraft:soul_sand", - "blockRuntimeId" : 5675 + "blockRuntimeId" : 5888 }, { "id" : "minecraft:netherrack", - "blockRuntimeId" : 4755 + "blockRuntimeId" : 4933 }, { "id" : "minecraft:magma", - "blockRuntimeId" : 4661 + "blockRuntimeId" : 4837 }, { "id" : "minecraft:nether_wart" }, { "id" : "minecraft:end_stone", - "blockRuntimeId" : 4003 + "blockRuntimeId" : 4145 }, { "id" : "minecraft:chorus_flower", - "blockRuntimeId" : 956 + "blockRuntimeId" : 1075 }, { "id" : "minecraft:chorus_plant", - "blockRuntimeId" : 962 + "blockRuntimeId" : 1081 }, { "id" : "minecraft:chorus_fruit" @@ -2305,51 +2489,51 @@ }, { "id" : "minecraft:sponge", - "blockRuntimeId" : 5683 + "blockRuntimeId" : 5896 }, { "id" : "minecraft:sponge", - "blockRuntimeId" : 5684 + "blockRuntimeId" : 5897 }, { "id" : "minecraft:coral_block", - "blockRuntimeId" : 3335 + "blockRuntimeId" : 3456 }, { "id" : "minecraft:coral_block", - "blockRuntimeId" : 3336 + "blockRuntimeId" : 3457 }, { "id" : "minecraft:coral_block", - "blockRuntimeId" : 3337 + "blockRuntimeId" : 3458 }, { "id" : "minecraft:coral_block", - "blockRuntimeId" : 3338 + "blockRuntimeId" : 3459 }, { "id" : "minecraft:coral_block", - "blockRuntimeId" : 3339 + "blockRuntimeId" : 3460 }, { "id" : "minecraft:coral_block", - "blockRuntimeId" : 3340 + "blockRuntimeId" : 3461 }, { "id" : "minecraft:coral_block", - "blockRuntimeId" : 3341 + "blockRuntimeId" : 3462 }, { "id" : "minecraft:coral_block", - "blockRuntimeId" : 3342 + "blockRuntimeId" : 3463 }, { "id" : "minecraft:coral_block", - "blockRuntimeId" : 3343 + "blockRuntimeId" : 3464 }, { "id" : "minecraft:coral_block", - "blockRuntimeId" : 3344 + "blockRuntimeId" : 3465 }, { "id" : "minecraft:leather_helmet" @@ -2762,6 +2946,9 @@ { "id" : "minecraft:saddle" }, + { + "id" : "minecraft:goat_horn" + }, { "id" : "minecraft:leather_horse_armor" }, @@ -3373,43 +3560,43 @@ }, { "id" : "minecraft:torch", - "blockRuntimeId" : 6075 + "blockRuntimeId" : 6289 }, { "id" : "minecraft:soul_torch", - "blockRuntimeId" : 5677 + "blockRuntimeId" : 5890 }, { "id" : "minecraft:sea_pickle", - "blockRuntimeId" : 5552 + "blockRuntimeId" : 5757 }, { "id" : "minecraft:lantern", - "blockRuntimeId" : 4482 + "blockRuntimeId" : 4652 }, { "id" : "minecraft:soul_lantern", - "blockRuntimeId" : 5673 + "blockRuntimeId" : 5886 }, { "id" : "minecraft:crafting_table", - "blockRuntimeId" : 3415 + "blockRuntimeId" : 3536 }, { "id" : "minecraft:cartography_table", - "blockRuntimeId" : 897 + "blockRuntimeId" : 938 }, { "id" : "minecraft:fletching_table", - "blockRuntimeId" : 4056 + "blockRuntimeId" : 4212 }, { "id" : "minecraft:smithing_table", - "blockRuntimeId" : 5600 + "blockRuntimeId" : 5813 }, { "id" : "minecraft:beehive", - "blockRuntimeId" : 244 + "blockRuntimeId" : 253 }, { "id" : "minecraft:campfire" @@ -3419,19 +3606,19 @@ }, { "id" : "minecraft:furnace", - "blockRuntimeId" : 4107 + "blockRuntimeId" : 4264 }, { "id" : "minecraft:blast_furnace", - "blockRuntimeId" : 611 + "blockRuntimeId" : 652 }, { "id" : "minecraft:smoker", - "blockRuntimeId" : 5601 + "blockRuntimeId" : 5814 }, { "id" : "minecraft:respawn_anchor", - "blockRuntimeId" : 5505 + "blockRuntimeId" : 5708 }, { "id" : "minecraft:brewing_stand" @@ -3450,121 +3637,121 @@ }, { "id" : "minecraft:grindstone", - "blockRuntimeId" : 4155 + "blockRuntimeId" : 4324 }, { "id" : "minecraft:enchanting_table", - "blockRuntimeId" : 3977 + "blockRuntimeId" : 4119 }, { "id" : "minecraft:bookshelf", - "blockRuntimeId" : 636 + "blockRuntimeId" : 677 }, { "id" : "minecraft:lectern", - "blockRuntimeId" : 4540 + "blockRuntimeId" : 4710 }, { "id" : "minecraft:cauldron" }, { "id" : "minecraft:composter", - "blockRuntimeId" : 3283 + "blockRuntimeId" : 3402 }, { "id" : "minecraft:chest", - "blockRuntimeId" : 948 + "blockRuntimeId" : 1067 }, { "id" : "minecraft:trapped_chest", - "blockRuntimeId" : 6097 + "blockRuntimeId" : 6311 }, { "id" : "minecraft:ender_chest", - "blockRuntimeId" : 4004 + "blockRuntimeId" : 4146 }, { "id" : "minecraft:barrel", - "blockRuntimeId" : 185 + "blockRuntimeId" : 194 }, { "id" : "minecraft:undyed_shulker_box", - "blockRuntimeId" : 6179 + "blockRuntimeId" : 6393 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5565 + "blockRuntimeId" : 5770 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5573 + "blockRuntimeId" : 5778 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5572 + "blockRuntimeId" : 5777 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5580 + "blockRuntimeId" : 5785 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5577 + "blockRuntimeId" : 5782 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5579 + "blockRuntimeId" : 5784 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5566 + "blockRuntimeId" : 5771 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5569 + "blockRuntimeId" : 5774 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5570 + "blockRuntimeId" : 5775 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5578 + "blockRuntimeId" : 5783 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5574 + "blockRuntimeId" : 5779 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5568 + "blockRuntimeId" : 5773 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5576 + "blockRuntimeId" : 5781 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5575 + "blockRuntimeId" : 5780 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5567 + "blockRuntimeId" : 5772 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5571 + "blockRuntimeId" : 5776 }, { "id" : "minecraft:armor_stand" }, { "id" : "minecraft:noteblock", - "blockRuntimeId" : 4765 + "blockRuntimeId" : 4943 }, { "id" : "minecraft:jukebox", - "blockRuntimeId" : 4327 + "blockRuntimeId" : 4497 }, { "id" : "minecraft:music_disc_13" @@ -3610,15 +3797,15 @@ }, { "id" : "minecraft:glowstone", - "blockRuntimeId" : 4117 + "blockRuntimeId" : 4286 }, { "id" : "minecraft:redstone_lamp", - "blockRuntimeId" : 5452 + "blockRuntimeId" : 5655 }, { "id" : "minecraft:sealantern", - "blockRuntimeId" : 5563 + "blockRuntimeId" : 5768 }, { "id" : "minecraft:oak_sign" @@ -3683,6 +3870,12 @@ { "id" : "minecraft:pufferfish_bucket" }, + { + "id" : "minecraft:powder_snow_bucket" + }, + { + "id" : "minecraft:glow_frame" + }, { "id" : "minecraft:skull", "damage" : 3 @@ -3708,23 +3901,23 @@ }, { "id" : "minecraft:beacon", - "blockRuntimeId" : 201 + "blockRuntimeId" : 210 }, { "id" : "minecraft:bell", - "blockRuntimeId" : 276 + "blockRuntimeId" : 285 }, { "id" : "minecraft:conduit", - "blockRuntimeId" : 3324 + "blockRuntimeId" : 3443 }, { "id" : "minecraft:stonecutter_block", - "blockRuntimeId" : 6014 + "blockRuntimeId" : 6228 }, { "id" : "minecraft:end_portal_frame", - "blockRuntimeId" : 3989 + "blockRuntimeId" : 4131 }, { "id" : "minecraft:coal" @@ -3738,6 +3931,9 @@ { "id" : "minecraft:iron_nugget" }, + { + "id" : "minecraft:copper_ingot" + }, { "id" : "minecraft:iron_ingot" }, @@ -3845,7 +4041,11 @@ }, { "id" : "minecraft:end_rod", - "blockRuntimeId" : 3997 + "blockRuntimeId" : 4139 + }, + { + "id" : "minecraft:lightning_rod", + "blockRuntimeId" : 4772 }, { "id" : "minecraft:end_crystal" @@ -4307,15 +4507,15 @@ }, { "id" : "minecraft:rail", - "blockRuntimeId" : 5386 + "blockRuntimeId" : 5589 }, { "id" : "minecraft:golden_rail", - "blockRuntimeId" : 4120 + "blockRuntimeId" : 4289 }, { "id" : "minecraft:detector_rail", - "blockRuntimeId" : 3724 + "blockRuntimeId" : 3856 }, { "id" : "minecraft:activator_rail", @@ -4338,74 +4538,74 @@ }, { "id" : "minecraft:redstone_block", - "blockRuntimeId" : 5451 + "blockRuntimeId" : 5654 }, { "id" : "minecraft:redstone_torch", - "blockRuntimeId" : 5454 + "blockRuntimeId" : 5657 }, { "id" : "minecraft:lever", - "blockRuntimeId" : 4548 + "blockRuntimeId" : 4718 }, { "id" : "minecraft:wooden_button", - "blockRuntimeId" : 6480 + "blockRuntimeId" : 6750 }, { "id" : "minecraft:spruce_button", - "blockRuntimeId" : 5685 + "blockRuntimeId" : 5899 }, { "id" : "minecraft:birch_button", - "blockRuntimeId" : 308 + "blockRuntimeId" : 349 }, { "id" : "minecraft:jungle_button", - "blockRuntimeId" : 4328 + "blockRuntimeId" : 4498 }, { "id" : "minecraft:acacia_button" }, { "id" : "minecraft:dark_oak_button", - "blockRuntimeId" : 3560 + "blockRuntimeId" : 3692 }, { "id" : "minecraft:stone_button", - "blockRuntimeId" : 5914 + "blockRuntimeId" : 6128 }, { "id" : "minecraft:crimson_button", - "blockRuntimeId" : 3416 + "blockRuntimeId" : 3537 }, { "id" : "minecraft:warped_button", - "blockRuntimeId" : 6247 + "blockRuntimeId" : 6461 }, { "id" : "minecraft:polished_blackstone_button", - "blockRuntimeId" : 5006 + "blockRuntimeId" : 5208 }, { "id" : "minecraft:tripwire_hook", - "blockRuntimeId" : 6119 + "blockRuntimeId" : 6333 }, { "id" : "minecraft:wooden_pressure_plate", - "blockRuntimeId" : 6524 + "blockRuntimeId" : 6794 }, { "id" : "minecraft:spruce_pressure_plate", - "blockRuntimeId" : 5745 + "blockRuntimeId" : 5959 }, { "id" : "minecraft:birch_pressure_plate", - "blockRuntimeId" : 368 + "blockRuntimeId" : 409 }, { "id" : "minecraft:jungle_pressure_plate", - "blockRuntimeId" : 4388 + "blockRuntimeId" : 4558 }, { "id" : "minecraft:acacia_pressure_plate", @@ -4413,39 +4613,39 @@ }, { "id" : "minecraft:dark_oak_pressure_plate", - "blockRuntimeId" : 3620 + "blockRuntimeId" : 3752 }, { "id" : "minecraft:crimson_pressure_plate", - "blockRuntimeId" : 3485 + "blockRuntimeId" : 3606 }, { "id" : "minecraft:warped_pressure_plate", - "blockRuntimeId" : 6316 + "blockRuntimeId" : 6530 }, { "id" : "minecraft:stone_pressure_plate", - "blockRuntimeId" : 5926 + "blockRuntimeId" : 6140 }, { "id" : "minecraft:light_weighted_pressure_plate", - "blockRuntimeId" : 4586 + "blockRuntimeId" : 4756 }, { "id" : "minecraft:heavy_weighted_pressure_plate", - "blockRuntimeId" : 4218 + "blockRuntimeId" : 4388 }, { "id" : "minecraft:polished_blackstone_pressure_plate", - "blockRuntimeId" : 5020 + "blockRuntimeId" : 5222 }, { "id" : "minecraft:observer", - "blockRuntimeId" : 4774 + "blockRuntimeId" : 4952 }, { "id" : "minecraft:daylight_detector", - "blockRuntimeId" : 3690 + "blockRuntimeId" : 3822 }, { "id" : "minecraft:repeater" @@ -4458,30 +4658,30 @@ }, { "id" : "minecraft:dropper", - "blockRuntimeId" : 3847 + "blockRuntimeId" : 3989 }, { "id" : "minecraft:dispenser", - "blockRuntimeId" : 3751 + "blockRuntimeId" : 3884 }, { "id" : "minecraft:piston", - "blockRuntimeId" : 4801 + "blockRuntimeId" : 4993 }, { "id" : "minecraft:sticky_piston", - "blockRuntimeId" : 5888 + "blockRuntimeId" : 6102 }, { "id" : "minecraft:tnt", - "blockRuntimeId" : 6071 + "blockRuntimeId" : 6285 }, { "id" : "minecraft:name_tag" }, { "id" : "minecraft:loom", - "blockRuntimeId" : 4651 + "blockRuntimeId" : 4827 }, { "id" : "minecraft:banner" @@ -4724,10 +4924,13 @@ }, { "id" : "minecraft:target", - "blockRuntimeId" : 6070 + "blockRuntimeId" : 6284 }, { "id" : "minecraft:lodestone_compass" + }, + { + "id" : "minecraft:item.glow_frame" } ] } \ No newline at end of file diff --git a/connector/src/main/resources/bedrock/runtime_item_states.json b/connector/src/main/resources/bedrock/runtime_item_states.json index eaf6656bd..6171ef9bf 100644 --- a/connector/src/main/resources/bedrock/runtime_item_states.json +++ b/connector/src/main/resources/bedrock/runtime_item_states.json @@ -1,7 +1,7 @@ [ { "name" : "minecraft:acacia_boat", - "id" : 377 + "id" : 380 }, { "name" : "minecraft:acacia_button", @@ -9,7 +9,7 @@ }, { "name" : "minecraft:acacia_door", - "id" : 546 + "id" : 552 }, { "name" : "minecraft:acacia_fence_gate", @@ -21,7 +21,7 @@ }, { "name" : "minecraft:acacia_sign", - "id" : 569 + "id" : 575 }, { "name" : "minecraft:acacia_stairs", @@ -45,7 +45,7 @@ }, { "name" : "minecraft:agent_spawn_egg", - "id" : 485 + "id" : 488 }, { "name" : "minecraft:air", @@ -73,19 +73,31 @@ }, { "name" : "minecraft:armor_stand", - "id" : 542 + "id" : 548 }, { "name" : "minecraft:arrow", "id" : 301 }, + { + "name" : "minecraft:azalea", + "id" : -337 + }, + { + "name" : "minecraft:azalea_leaves", + "id" : -324 + }, + { + "name" : "minecraft:azalea_leaves_flowered", + "id" : -325 + }, { "name" : "minecraft:baked_potato", "id" : 281 }, { "name" : "minecraft:balloon", - "id" : 587 + "id" : 593 }, { "name" : "minecraft:bamboo", @@ -97,11 +109,11 @@ }, { "name" : "minecraft:banner", - "id" : 557 + "id" : 563 }, { "name" : "minecraft:banner_pattern", - "id" : 613 + "id" : 621 }, { "name" : "minecraft:barrel", @@ -117,7 +129,7 @@ }, { "name" : "minecraft:bat_spawn_egg", - "id" : 451 + "id" : 454 }, { "name" : "minecraft:beacon", @@ -125,7 +137,7 @@ }, { "name" : "minecraft:bed", - "id" : 416 + "id" : 419 }, { "name" : "minecraft:bedrock", @@ -137,7 +149,7 @@ }, { "name" : "minecraft:bee_spawn_egg", - "id" : 492 + "id" : 495 }, { "name" : "minecraft:beef", @@ -163,9 +175,13 @@ "name" : "minecraft:bell", "id" : -206 }, + { + "name" : "minecraft:big_dripleaf", + "id" : -323 + }, { "name" : "minecraft:birch_boat", - "id" : 374 + "id" : 377 }, { "name" : "minecraft:birch_button", @@ -173,7 +189,7 @@ }, { "name" : "minecraft:birch_door", - "id" : 544 + "id" : 550 }, { "name" : "minecraft:birch_fence_gate", @@ -185,7 +201,7 @@ }, { "name" : "minecraft:birch_sign", - "id" : 567 + "id" : 573 }, { "name" : "minecraft:birch_stairs", @@ -205,7 +221,7 @@ }, { "name" : "minecraft:black_dye", - "id" : 393 + "id" : 396 }, { "name" : "minecraft:black_glazed_terracotta", @@ -237,23 +253,23 @@ }, { "name" : "minecraft:blaze_powder", - "id" : 427 + "id" : 430 }, { "name" : "minecraft:blaze_rod", - "id" : 421 + "id" : 424 }, { "name" : "minecraft:blaze_spawn_egg", - "id" : 454 + "id" : 457 }, { "name" : "minecraft:bleach", - "id" : 585 + "id" : 591 }, { "name" : "minecraft:blue_dye", - "id" : 397 + "id" : 400 }, { "name" : "minecraft:blue_glazed_terracotta", @@ -265,11 +281,11 @@ }, { "name" : "minecraft:boat", - "id" : 611 + "id" : 619 }, { "name" : "minecraft:bone", - "id" : 413 + "id" : 416 }, { "name" : "minecraft:bone_block", @@ -277,11 +293,11 @@ }, { "name" : "minecraft:bone_meal", - "id" : 409 + "id" : 412 }, { "name" : "minecraft:book", - "id" : 385 + "id" : 388 }, { "name" : "minecraft:bookshelf", @@ -293,7 +309,7 @@ }, { "name" : "minecraft:bordure_indented_banner_pattern", - "id" : 576 + "id" : 582 }, { "name" : "minecraft:bow", @@ -309,7 +325,7 @@ }, { "name" : "minecraft:brewing_stand", - "id" : 429 + "id" : 432 }, { "name" : "minecraft:brewingstandblock", @@ -317,7 +333,7 @@ }, { "name" : "minecraft:brick", - "id" : 381 + "id" : 384 }, { "name" : "minecraft:brick_block", @@ -329,7 +345,7 @@ }, { "name" : "minecraft:brown_dye", - "id" : 396 + "id" : 399 }, { "name" : "minecraft:brown_glazed_terracotta", @@ -357,15 +373,15 @@ }, { "name" : "minecraft:cake", - "id" : 415 + "id" : 418 }, { "name" : "minecraft:camera", - "id" : 582 + "id" : 588 }, { "name" : "minecraft:campfire", - "id" : 578 + "id" : 584 }, { "name" : "minecraft:carpet", @@ -377,7 +393,7 @@ }, { "name" : "minecraft:carrot_on_a_stick", - "id" : 507 + "id" : 513 }, { "name" : "minecraft:carrots", @@ -393,19 +409,31 @@ }, { "name" : "minecraft:cat_spawn_egg", - "id" : 486 + "id" : 489 }, { "name" : "minecraft:cauldron", - "id" : 430 + "id" : 433 }, { "name" : "minecraft:cave_spider_spawn_egg", - "id" : 455 + "id" : 458 + }, + { + "name" : "minecraft:cave_vines", + "id" : -322 + }, + { + "name" : "minecraft:cave_vines_body_with_berries", + "id" : -375 + }, + { + "name" : "minecraft:cave_vines_head_with_berries", + "id" : -376 }, { "name" : "minecraft:chain", - "id" : 607 + "id" : 613 }, { "name" : "minecraft:chain_command_block", @@ -445,7 +473,7 @@ }, { "name" : "minecraft:chest_minecart", - "id" : 387 + "id" : 390 }, { "name" : "minecraft:chicken", @@ -453,7 +481,7 @@ }, { "name" : "minecraft:chicken_spawn_egg", - "id" : 433 + "id" : 436 }, { "name" : "minecraft:chiseled_nether_bricks", @@ -469,7 +497,7 @@ }, { "name" : "minecraft:chorus_fruit", - "id" : 548 + "id" : 554 }, { "name" : "minecraft:chorus_plant", @@ -481,11 +509,11 @@ }, { "name" : "minecraft:clay_ball", - "id" : 382 + "id" : 385 }, { "name" : "minecraft:clock", - "id" : 391 + "id" : 394 }, { "name" : "minecraft:coal", @@ -513,7 +541,7 @@ }, { "name" : "minecraft:cocoa_beans", - "id" : 410 + "id" : 413 }, { "name" : "minecraft:cod", @@ -525,7 +553,7 @@ }, { "name" : "minecraft:cod_spawn_egg", - "id" : 478 + "id" : 481 }, { "name" : "minecraft:colored_torch_bp", @@ -541,15 +569,15 @@ }, { "name" : "minecraft:command_block_minecart", - "id" : 553 + "id" : 559 }, { "name" : "minecraft:comparator", - "id" : 512 + "id" : 518 }, { "name" : "minecraft:compass", - "id" : 389 + "id" : 392 }, { "name" : "minecraft:composter", @@ -557,7 +585,7 @@ }, { "name" : "minecraft:compound", - "id" : 583 + "id" : 589 }, { "name" : "minecraft:concrete", @@ -585,7 +613,7 @@ }, { "name" : "minecraft:cooked_mutton", - "id" : 541 + "id" : 547 }, { "name" : "minecraft:cooked_porkchop", @@ -603,6 +631,18 @@ "name" : "minecraft:cookie", "id" : 271 }, + { + "name" : "minecraft:copper_block", + "id" : -340 + }, + { + "name" : "minecraft:copper_ingot", + "id" : 502 + }, + { + "name" : "minecraft:copper_ore", + "id" : -311 + }, { "name" : "minecraft:coral", "id" : -131 @@ -633,7 +673,7 @@ }, { "name" : "minecraft:cow_spawn_egg", - "id" : 434 + "id" : 437 }, { "name" : "minecraft:cracked_nether_bricks", @@ -649,11 +689,11 @@ }, { "name" : "minecraft:creeper_banner_pattern", - "id" : 572 + "id" : 578 }, { "name" : "minecraft:creeper_spawn_egg", - "id" : 439 + "id" : 442 }, { "name" : "minecraft:crimson_button", @@ -661,7 +701,7 @@ }, { "name" : "minecraft:crimson_door", - "id" : 604 + "id" : 610 }, { "name" : "minecraft:crimson_double_slab", @@ -701,7 +741,7 @@ }, { "name" : "minecraft:crimson_sign", - "id" : 602 + "id" : 608 }, { "name" : "minecraft:crimson_slab", @@ -729,15 +769,27 @@ }, { "name" : "minecraft:crossbow", - "id" : 565 + "id" : 571 }, { "name" : "minecraft:crying_obsidian", "id" : -289 }, + { + "name" : "minecraft:cut_copper", + "id" : -347 + }, + { + "name" : "minecraft:cut_copper_slab", + "id" : -361 + }, + { + "name" : "minecraft:cut_copper_stairs", + "id" : -354 + }, { "name" : "minecraft:cyan_dye", - "id" : 399 + "id" : 402 }, { "name" : "minecraft:cyan_glazed_terracotta", @@ -745,7 +797,7 @@ }, { "name" : "minecraft:dark_oak_boat", - "id" : 378 + "id" : 381 }, { "name" : "minecraft:dark_oak_button", @@ -753,7 +805,7 @@ }, { "name" : "minecraft:dark_oak_door", - "id" : 547 + "id" : 553 }, { "name" : "minecraft:dark_oak_fence_gate", @@ -765,7 +817,7 @@ }, { "name" : "minecraft:dark_oak_sign", - "id" : 570 + "id" : 576 }, { "name" : "minecraft:dark_oak_stairs", @@ -837,7 +889,7 @@ }, { "name" : "minecraft:diamond_horse_armor", - "id" : 523 + "id" : 529 }, { "name" : "minecraft:diamond_leggings", @@ -867,17 +919,25 @@ "name" : "minecraft:dirt", "id" : 3 }, + { + "name" : "minecraft:dirt_with_roots", + "id" : -318 + }, { "name" : "minecraft:dispenser", "id" : 23 }, { "name" : "minecraft:dolphin_spawn_egg", - "id" : 482 + "id" : 485 }, { "name" : "minecraft:donkey_spawn_egg", - "id" : 463 + "id" : 466 + }, + { + "name" : "minecraft:double_cut_copper_slab", + "id" : -368 }, { "name" : "minecraft:double_plant", @@ -905,7 +965,7 @@ }, { "name" : "minecraft:dragon_breath", - "id" : 550 + "id" : 556 }, { "name" : "minecraft:dragon_egg", @@ -919,25 +979,29 @@ "name" : "minecraft:dried_kelp_block", "id" : -139 }, + { + "name" : "minecraft:dripstone_block", + "id" : -317 + }, { "name" : "minecraft:dropper", "id" : 125 }, { "name" : "minecraft:drowned_spawn_egg", - "id" : 481 + "id" : 484 }, { "name" : "minecraft:dye", - "id" : 612 + "id" : 620 }, { "name" : "minecraft:egg", - "id" : 388 + "id" : 391 }, { "name" : "minecraft:elder_guardian_spawn_egg", - "id" : 469 + "id" : 472 }, { "name" : "minecraft:element_0", @@ -1417,11 +1481,11 @@ }, { "name" : "minecraft:elytra", - "id" : 554 + "id" : 560 }, { "name" : "minecraft:emerald", - "id" : 502 + "id" : 508 }, { "name" : "minecraft:emerald_block", @@ -1433,11 +1497,11 @@ }, { "name" : "minecraft:empty_map", - "id" : 505 + "id" : 511 }, { "name" : "minecraft:enchanted_book", - "id" : 511 + "id" : 517 }, { "name" : "minecraft:enchanted_golden_apple", @@ -1457,7 +1521,7 @@ }, { "name" : "minecraft:end_crystal", - "id" : 615 + "id" : 623 }, { "name" : "minecraft:end_gateway", @@ -1485,27 +1549,47 @@ }, { "name" : "minecraft:ender_eye", - "id" : 431 + "id" : 434 }, { "name" : "minecraft:ender_pearl", - "id" : 420 + "id" : 423 }, { "name" : "minecraft:enderman_spawn_egg", - "id" : 440 + "id" : 443 }, { "name" : "minecraft:endermite_spawn_egg", - "id" : 458 + "id" : 461 }, { "name" : "minecraft:evoker_spawn_egg", - "id" : 473 + "id" : 476 }, { "name" : "minecraft:experience_bottle", - "id" : 498 + "id" : 504 + }, + { + "name" : "minecraft:exposed_copper", + "id" : -341 + }, + { + "name" : "minecraft:exposed_cut_copper", + "id" : -348 + }, + { + "name" : "minecraft:exposed_cut_copper_slab", + "id" : -362 + }, + { + "name" : "minecraft:exposed_cut_copper_stairs", + "id" : -355 + }, + { + "name" : "minecraft:exposed_double_cut_copper_slab", + "id" : -369 }, { "name" : "minecraft:farmland", @@ -1525,15 +1609,15 @@ }, { "name" : "minecraft:fermented_spider_eye", - "id" : 426 + "id" : 429 }, { "name" : "minecraft:field_masoned_banner_pattern", - "id" : 575 + "id" : 581 }, { "name" : "minecraft:filled_map", - "id" : 418 + "id" : 421 }, { "name" : "minecraft:fire", @@ -1541,19 +1625,19 @@ }, { "name" : "minecraft:fire_charge", - "id" : 499 + "id" : 505 }, { "name" : "minecraft:firework_rocket", - "id" : 509 + "id" : 515 }, { "name" : "minecraft:firework_star", - "id" : 510 + "id" : 516 }, { "name" : "minecraft:fishing_rod", - "id" : 390 + "id" : 393 }, { "name" : "minecraft:fletching_table", @@ -1569,11 +1653,15 @@ }, { "name" : "minecraft:flower_banner_pattern", - "id" : 571 + "id" : 577 }, { "name" : "minecraft:flower_pot", - "id" : 504 + "id" : 510 + }, + { + "name" : "minecraft:flowering_azalea", + "id" : -338 }, { "name" : "minecraft:flowing_lava", @@ -1585,11 +1673,11 @@ }, { "name" : "minecraft:fox_spawn_egg", - "id" : 488 + "id" : 491 }, { "name" : "minecraft:frame", - "id" : 503 + "id" : 509 }, { "name" : "minecraft:frosted_ice", @@ -1601,11 +1689,11 @@ }, { "name" : "minecraft:ghast_spawn_egg", - "id" : 452 + "id" : 455 }, { "name" : "minecraft:ghast_tear", - "id" : 422 + "id" : 425 }, { "name" : "minecraft:gilded_blackstone", @@ -1617,7 +1705,7 @@ }, { "name" : "minecraft:glass_bottle", - "id" : 425 + "id" : 428 }, { "name" : "minecraft:glass_pane", @@ -1625,7 +1713,23 @@ }, { "name" : "minecraft:glistering_melon_slice", - "id" : 432 + "id" : 435 + }, + { + "name" : "minecraft:glow_berries", + "id" : 369 + }, + { + "name" : "minecraft:glow_frame", + "id" : 618 + }, + { + "name" : "minecraft:glow_ink_sac", + "id" : 370 + }, + { + "name" : "minecraft:glow_squid_spawn_egg", + "id" : 503 }, { "name" : "minecraft:glow_stick", @@ -1641,7 +1745,15 @@ }, { "name" : "minecraft:glowstone_dust", - "id" : 392 + "id" : 395 + }, + { + "name" : "minecraft:goat_horn", + "id" : 617 + }, + { + "name" : "minecraft:goat_spawn_egg", + "id" : 501 }, { "name" : "minecraft:gold_block", @@ -1653,7 +1765,7 @@ }, { "name" : "minecraft:gold_nugget", - "id" : 423 + "id" : 426 }, { "name" : "minecraft:gold_ore", @@ -1689,7 +1801,7 @@ }, { "name" : "minecraft:golden_horse_armor", - "id" : 522 + "id" : 528 }, { "name" : "minecraft:golden_leggings", @@ -1729,7 +1841,7 @@ }, { "name" : "minecraft:gray_dye", - "id" : 401 + "id" : 404 }, { "name" : "minecraft:gray_glazed_terracotta", @@ -1737,7 +1849,7 @@ }, { "name" : "minecraft:green_dye", - "id" : 395 + "id" : 398 }, { "name" : "minecraft:green_glazed_terracotta", @@ -1749,12 +1861,16 @@ }, { "name" : "minecraft:guardian_spawn_egg", - "id" : 459 + "id" : 462 }, { "name" : "minecraft:gunpowder", "id" : 328 }, + { + "name" : "minecraft:hanging_roots", + "id" : -319 + }, { "name" : "minecraft:hard_glass", "id" : 253 @@ -1781,7 +1897,7 @@ }, { "name" : "minecraft:heart_of_the_sea", - "id" : 561 + "id" : 567 }, { "name" : "minecraft:heavy_weighted_pressure_plate", @@ -1789,7 +1905,7 @@ }, { "name" : "minecraft:hoglin_spawn_egg", - "id" : 494 + "id" : 497 }, { "name" : "minecraft:honey_block", @@ -1797,11 +1913,11 @@ }, { "name" : "minecraft:honey_bottle", - "id" : 581 + "id" : 587 }, { "name" : "minecraft:honeycomb", - "id" : 580 + "id" : 586 }, { "name" : "minecraft:honeycomb_block", @@ -1809,19 +1925,19 @@ }, { "name" : "minecraft:hopper", - "id" : 517 + "id" : 523 }, { "name" : "minecraft:hopper_minecart", - "id" : 516 + "id" : 522 }, { "name" : "minecraft:horse_spawn_egg", - "id" : 456 + "id" : 459 }, { "name" : "minecraft:husk_spawn_egg", - "id" : 461 + "id" : 464 }, { "name" : "minecraft:ice", @@ -1829,7 +1945,7 @@ }, { "name" : "minecraft:ice_bomb", - "id" : 584 + "id" : 590 }, { "name" : "minecraft:info_update", @@ -1841,7 +1957,7 @@ }, { "name" : "minecraft:ink_sac", - "id" : 411 + "id" : 414 }, { "name" : "minecraft:invisiblebedrock", @@ -1869,7 +1985,7 @@ }, { "name" : "minecraft:iron_door", - "id" : 370 + "id" : 373 }, { "name" : "minecraft:iron_helmet", @@ -1881,7 +1997,7 @@ }, { "name" : "minecraft:iron_horse_armor", - "id" : 521 + "id" : 527 }, { "name" : "minecraft:iron_ingot", @@ -1893,7 +2009,7 @@ }, { "name" : "minecraft:iron_nugget", - "id" : 559 + "id" : 565 }, { "name" : "minecraft:iron_ore", @@ -1967,6 +2083,10 @@ "name" : "minecraft:item.frame", "id" : 199 }, + { + "name" : "minecraft:item.glow_frame", + "id" : -339 + }, { "name" : "minecraft:item.hopper", "id" : 154 @@ -1983,10 +2103,6 @@ "name" : "minecraft:item.kelp", "id" : -138 }, - { - "name" : "minecraft:nether_brick", - "id" : 112 - }, { "name" : "minecraft:item.nether_sprouts", "id" : -238 @@ -2033,7 +2149,7 @@ }, { "name" : "minecraft:jungle_boat", - "id" : 375 + "id" : 378 }, { "name" : "minecraft:jungle_button", @@ -2041,7 +2157,7 @@ }, { "name" : "minecraft:jungle_door", - "id" : 545 + "id" : 551 }, { "name" : "minecraft:jungle_fence_gate", @@ -2053,7 +2169,7 @@ }, { "name" : "minecraft:jungle_sign", - "id" : 568 + "id" : 574 }, { "name" : "minecraft:jungle_stairs", @@ -2073,7 +2189,7 @@ }, { "name" : "minecraft:kelp", - "id" : 380 + "id" : 383 }, { "name" : "minecraft:ladder", @@ -2089,7 +2205,7 @@ }, { "name" : "minecraft:lapis_lazuli", - "id" : 412 + "id" : 415 }, { "name" : "minecraft:lapis_ore", @@ -2109,11 +2225,11 @@ }, { "name" : "minecraft:lead", - "id" : 537 + "id" : 543 }, { "name" : "minecraft:leather", - "id" : 379 + "id" : 382 }, { "name" : "minecraft:leather_boots", @@ -2129,7 +2245,7 @@ }, { "name" : "minecraft:leather_horse_armor", - "id" : 520 + "id" : 526 }, { "name" : "minecraft:leather_leggings", @@ -2157,7 +2273,7 @@ }, { "name" : "minecraft:light_blue_dye", - "id" : 405 + "id" : 408 }, { "name" : "minecraft:light_blue_glazed_terracotta", @@ -2165,15 +2281,19 @@ }, { "name" : "minecraft:light_gray_dye", - "id" : 400 + "id" : 403 }, { "name" : "minecraft:light_weighted_pressure_plate", "id" : 147 }, + { + "name" : "minecraft:lightning_rod", + "id" : -312 + }, { "name" : "minecraft:lime_dye", - "id" : 403 + "id" : 406 }, { "name" : "minecraft:lime_glazed_terracotta", @@ -2181,7 +2301,7 @@ }, { "name" : "minecraft:lingering_potion", - "id" : 552 + "id" : 558 }, { "name" : "minecraft:lit_blast_furnace", @@ -2209,7 +2329,7 @@ }, { "name" : "minecraft:llama_spawn_egg", - "id" : 471 + "id" : 474 }, { "name" : "minecraft:lodestone", @@ -2217,7 +2337,7 @@ }, { "name" : "minecraft:lodestone_compass", - "id" : 590 + "id" : 596 }, { "name" : "minecraft:log", @@ -2233,7 +2353,7 @@ }, { "name" : "minecraft:magenta_dye", - "id" : 406 + "id" : 409 }, { "name" : "minecraft:magenta_glazed_terracotta", @@ -2245,15 +2365,15 @@ }, { "name" : "minecraft:magma_cream", - "id" : 428 + "id" : 431 }, { "name" : "minecraft:magma_cube_spawn_egg", - "id" : 453 + "id" : 456 }, { "name" : "minecraft:medicine", - "id" : 588 + "id" : 594 }, { "name" : "minecraft:melon_block", @@ -2277,7 +2397,7 @@ }, { "name" : "minecraft:minecart", - "id" : 368 + "id" : 371 }, { "name" : "minecraft:mob_spawner", @@ -2285,7 +2405,7 @@ }, { "name" : "minecraft:mojang_banner_pattern", - "id" : 574 + "id" : 580 }, { "name" : "minecraft:monster_egg", @@ -2293,7 +2413,15 @@ }, { "name" : "minecraft:mooshroom_spawn_egg", - "id" : 438 + "id" : 441 + }, + { + "name" : "minecraft:moss_block", + "id" : -320 + }, + { + "name" : "minecraft:moss_carpet", + "id" : -335 }, { "name" : "minecraft:mossy_cobblestone", @@ -2313,7 +2441,7 @@ }, { "name" : "minecraft:mule_spawn_egg", - "id" : 464 + "id" : 467 }, { "name" : "minecraft:mushroom_stew", @@ -2321,59 +2449,59 @@ }, { "name" : "minecraft:music_disc_11", - "id" : 534 + "id" : 540 }, { "name" : "minecraft:music_disc_13", - "id" : 524 - }, - { - "name" : "minecraft:music_disc_blocks", - "id" : 526 - }, - { - "name" : "minecraft:music_disc_cat", - "id" : 525 - }, - { - "name" : "minecraft:music_disc_chirp", - "id" : 527 - }, - { - "name" : "minecraft:music_disc_far", - "id" : 528 - }, - { - "name" : "minecraft:music_disc_mall", - "id" : 529 - }, - { - "name" : "minecraft:music_disc_mellohi", "id" : 530 }, { - "name" : "minecraft:music_disc_pigstep", - "id" : 608 - }, - { - "name" : "minecraft:music_disc_stal", - "id" : 531 - }, - { - "name" : "minecraft:music_disc_strad", + "name" : "minecraft:music_disc_blocks", "id" : 532 }, { - "name" : "minecraft:music_disc_wait", - "id" : 535 + "name" : "minecraft:music_disc_cat", + "id" : 531 }, { - "name" : "minecraft:music_disc_ward", + "name" : "minecraft:music_disc_chirp", "id" : 533 }, + { + "name" : "minecraft:music_disc_far", + "id" : 534 + }, + { + "name" : "minecraft:music_disc_mall", + "id" : 535 + }, + { + "name" : "minecraft:music_disc_mellohi", + "id" : 536 + }, + { + "name" : "minecraft:music_disc_pigstep", + "id" : 614 + }, + { + "name" : "minecraft:music_disc_stal", + "id" : 537 + }, + { + "name" : "minecraft:music_disc_strad", + "id" : 538 + }, + { + "name" : "minecraft:music_disc_wait", + "id" : 541 + }, + { + "name" : "minecraft:music_disc_ward", + "id" : 539 + }, { "name" : "minecraft:mutton", - "id" : 540 + "id" : 546 }, { "name" : "minecraft:mycelium", @@ -2381,15 +2509,15 @@ }, { "name" : "minecraft:name_tag", - "id" : 538 + "id" : 544 }, { "name" : "minecraft:nautilus_shell", - "id" : 560 + "id" : 566 }, { - "name" : "minecraft:netherbrick", - "id" : 513 + "name" : "minecraft:nether_brick", + "id" : 112 }, { "name" : "minecraft:nether_brick_fence", @@ -2405,11 +2533,11 @@ }, { "name" : "minecraft:nether_sprouts", - "id" : 609 + "id" : 615 }, { "name" : "minecraft:nether_star", - "id" : 508 + "id" : 514 }, { "name" : "minecraft:nether_wart", @@ -2419,9 +2547,13 @@ "name" : "minecraft:nether_wart_block", "id" : 214 }, + { + "name" : "minecraft:netherbrick", + "id" : 519 + }, { "name" : "minecraft:netherite_axe", - "id" : 595 + "id" : 601 }, { "name" : "minecraft:netherite_block", @@ -2429,43 +2561,43 @@ }, { "name" : "minecraft:netherite_boots", - "id" : 600 + "id" : 606 }, { "name" : "minecraft:netherite_chestplate", - "id" : 598 + "id" : 604 }, { "name" : "minecraft:netherite_helmet", - "id" : 597 + "id" : 603 }, { "name" : "minecraft:netherite_hoe", - "id" : 596 + "id" : 602 }, { "name" : "minecraft:netherite_ingot", - "id" : 591 + "id" : 597 }, { "name" : "minecraft:netherite_leggings", - "id" : 599 + "id" : 605 }, { "name" : "minecraft:netherite_pickaxe", - "id" : 594 + "id" : 600 }, { "name" : "minecraft:netherite_scrap", - "id" : 601 + "id" : 607 }, { "name" : "minecraft:netherite_shovel", - "id" : 593 + "id" : 599 }, { "name" : "minecraft:netherite_sword", - "id" : 592 + "id" : 598 }, { "name" : "minecraft:netherrack", @@ -2485,11 +2617,11 @@ }, { "name" : "minecraft:npc_spawn_egg", - "id" : 468 + "id" : 471 }, { "name" : "minecraft:oak_boat", - "id" : 373 + "id" : 376 }, { "name" : "minecraft:oak_sign", @@ -2509,16 +2641,36 @@ }, { "name" : "minecraft:ocelot_spawn_egg", - "id" : 449 + "id" : 452 }, { "name" : "minecraft:orange_dye", - "id" : 407 + "id" : 410 }, { "name" : "minecraft:orange_glazed_terracotta", "id" : 221 }, + { + "name" : "minecraft:oxidized_copper", + "id" : -343 + }, + { + "name" : "minecraft:oxidized_cut_copper", + "id" : -350 + }, + { + "name" : "minecraft:oxidized_cut_copper_slab", + "id" : -364 + }, + { + "name" : "minecraft:oxidized_cut_copper_stairs", + "id" : -357 + }, + { + "name" : "minecraft:oxidized_double_cut_copper_slab", + "id" : -371 + }, { "name" : "minecraft:packed_ice", "id" : 174 @@ -2529,47 +2681,47 @@ }, { "name" : "minecraft:panda_spawn_egg", - "id" : 487 + "id" : 490 }, { "name" : "minecraft:paper", - "id" : 384 + "id" : 387 }, { "name" : "minecraft:parrot_spawn_egg", - "id" : 476 + "id" : 479 }, { "name" : "minecraft:phantom_membrane", - "id" : 564 + "id" : 570 }, { "name" : "minecraft:phantom_spawn_egg", - "id" : 484 + "id" : 487 }, { "name" : "minecraft:pig_spawn_egg", - "id" : 435 + "id" : 438 }, { "name" : "minecraft:piglin_banner_pattern", - "id" : 577 + "id" : 583 }, { "name" : "minecraft:piglin_brute_spawn_egg", - "id" : 497 + "id" : 500 }, { "name" : "minecraft:piglin_spawn_egg", - "id" : 495 + "id" : 498 }, { "name" : "minecraft:pillager_spawn_egg", - "id" : 489 + "id" : 492 }, { "name" : "minecraft:pink_dye", - "id" : 402 + "id" : 405 }, { "name" : "minecraft:pink_glazed_terracotta", @@ -2591,13 +2743,17 @@ "name" : "minecraft:podzol", "id" : 243 }, + { + "name" : "minecraft:pointed_dripstone", + "id" : -308 + }, { "name" : "minecraft:poisonous_potato", "id" : 282 }, { "name" : "minecraft:polar_bear_spawn_egg", - "id" : 470 + "id" : 473 }, { "name" : "minecraft:polished_andesite_stairs", @@ -2665,7 +2821,7 @@ }, { "name" : "minecraft:popped_chorus_fruit", - "id" : 549 + "id" : 555 }, { "name" : "minecraft:porkchop", @@ -2685,7 +2841,15 @@ }, { "name" : "minecraft:potion", - "id" : 424 + "id" : 427 + }, + { + "name" : "minecraft:powder_snow", + "id" : -306 + }, + { + "name" : "minecraft:powder_snow_bucket", + "id" : 368 }, { "name" : "minecraft:powered_comparator", @@ -2705,11 +2869,11 @@ }, { "name" : "minecraft:prismarine_crystals", - "id" : 539 + "id" : 545 }, { "name" : "minecraft:prismarine_shard", - "id" : 555 + "id" : 561 }, { "name" : "minecraft:prismarine_stairs", @@ -2725,7 +2889,7 @@ }, { "name" : "minecraft:pufferfish_spawn_egg", - "id" : 479 + "id" : 482 }, { "name" : "minecraft:pumpkin", @@ -2745,7 +2909,7 @@ }, { "name" : "minecraft:purple_dye", - "id" : 398 + "id" : 401 }, { "name" : "minecraft:purple_glazed_terracotta", @@ -2761,7 +2925,7 @@ }, { "name" : "minecraft:quartz", - "id" : 514 + "id" : 520 }, { "name" : "minecraft:quartz_block", @@ -2785,15 +2949,15 @@ }, { "name" : "minecraft:rabbit_foot", - "id" : 518 + "id" : 524 }, { "name" : "minecraft:rabbit_hide", - "id" : 519 + "id" : 525 }, { "name" : "minecraft:rabbit_spawn_egg", - "id" : 457 + "id" : 460 }, { "name" : "minecraft:rabbit_stew", @@ -2805,11 +2969,11 @@ }, { "name" : "minecraft:rapid_fertilizer", - "id" : 586 + "id" : 592 }, { "name" : "minecraft:ravager_spawn_egg", - "id" : 491 + "id" : 494 }, { "name" : "minecraft:real_double_stone_slab", @@ -2829,7 +2993,7 @@ }, { "name" : "minecraft:red_dye", - "id" : 394 + "id" : 397 }, { "name" : "minecraft:red_flower", @@ -2865,7 +3029,7 @@ }, { "name" : "minecraft:redstone", - "id" : 371 + "id" : 374 }, { "name" : "minecraft:redstone_block", @@ -2889,7 +3053,7 @@ }, { "name" : "minecraft:repeater", - "id" : 417 + "id" : 420 }, { "name" : "minecraft:repeating_command_block", @@ -2909,7 +3073,7 @@ }, { "name" : "minecraft:saddle", - "id" : 369 + "id" : 372 }, { "name" : "minecraft:salmon", @@ -2921,7 +3085,7 @@ }, { "name" : "minecraft:salmon_spawn_egg", - "id" : 480 + "id" : 483 }, { "name" : "minecraft:sand", @@ -2943,9 +3107,13 @@ "name" : "minecraft:scaffolding", "id" : -165 }, + { + "name" : "minecraft:sculk_sensor", + "id" : -307 + }, { "name" : "minecraft:scute", - "id" : 562 + "id" : 568 }, { "name" : "minecraft:sea_pickle", @@ -2961,11 +3129,11 @@ }, { "name" : "minecraft:shears", - "id" : 419 + "id" : 422 }, { "name" : "minecraft:sheep_spawn_egg", - "id" : 436 + "id" : 439 }, { "name" : "minecraft:shield", @@ -2981,11 +3149,11 @@ }, { "name" : "minecraft:shulker_shell", - "id" : 556 + "id" : 562 }, { "name" : "minecraft:shulker_spawn_egg", - "id" : 467 + "id" : 470 }, { "name" : "minecraft:silver_glazed_terracotta", @@ -2993,23 +3161,23 @@ }, { "name" : "minecraft:silverfish_spawn_egg", - "id" : 441 + "id" : 444 }, { "name" : "minecraft:skeleton_horse_spawn_egg", - "id" : 465 + "id" : 468 }, { "name" : "minecraft:skeleton_spawn_egg", - "id" : 442 + "id" : 445 }, { "name" : "minecraft:skull", - "id" : 506 + "id" : 512 }, { "name" : "minecraft:skull_banner_pattern", - "id" : 573 + "id" : 579 }, { "name" : "minecraft:slime", @@ -3017,11 +3185,15 @@ }, { "name" : "minecraft:slime_ball", - "id" : 386 + "id" : 389 }, { "name" : "minecraft:slime_spawn_egg", - "id" : 443 + "id" : 446 + }, + { + "name" : "minecraft:small_dripleaf_block", + "id" : -336 }, { "name" : "minecraft:smithing_table", @@ -3057,11 +3229,11 @@ }, { "name" : "minecraft:snowball", - "id" : 372 + "id" : 375 }, { "name" : "minecraft:soul_campfire", - "id" : 610 + "id" : 616 }, { "name" : "minecraft:soul_fire", @@ -3085,11 +3257,11 @@ }, { "name" : "minecraft:sparkler", - "id" : 589 + "id" : 595 }, { "name" : "minecraft:spawn_egg", - "id" : 614 + "id" : 622 }, { "name" : "minecraft:spider_eye", @@ -3097,19 +3269,23 @@ }, { "name" : "minecraft:spider_spawn_egg", - "id" : 444 + "id" : 447 }, { "name" : "minecraft:splash_potion", - "id" : 551 + "id" : 557 }, { "name" : "minecraft:sponge", "id" : 19 }, + { + "name" : "minecraft:spore_blossom", + "id" : -321 + }, { "name" : "minecraft:spruce_boat", - "id" : 376 + "id" : 379 }, { "name" : "minecraft:spruce_button", @@ -3117,7 +3293,7 @@ }, { "name" : "minecraft:spruce_door", - "id" : 543 + "id" : 549 }, { "name" : "minecraft:spruce_fence_gate", @@ -3129,7 +3305,7 @@ }, { "name" : "minecraft:spruce_sign", - "id" : 566 + "id" : 572 }, { "name" : "minecraft:spruce_stairs", @@ -3149,7 +3325,7 @@ }, { "name" : "minecraft:squid_spawn_egg", - "id" : 448 + "id" : 451 }, { "name" : "minecraft:stained_glass", @@ -3237,11 +3413,11 @@ }, { "name" : "minecraft:stray_spawn_egg", - "id" : 460 + "id" : 463 }, { "name" : "minecraft:strider_spawn_egg", - "id" : 493 + "id" : 496 }, { "name" : "minecraft:string", @@ -3297,15 +3473,15 @@ }, { "name" : "minecraft:sugar", - "id" : 414 + "id" : 417 }, { "name" : "minecraft:sugar_cane", - "id" : 383 + "id" : 386 }, { "name" : "minecraft:suspicious_stew", - "id" : 579 + "id" : 585 }, { "name" : "minecraft:sweet_berries", @@ -3329,7 +3505,7 @@ }, { "name" : "minecraft:tnt_minecart", - "id" : 515 + "id" : 521 }, { "name" : "minecraft:torch", @@ -3337,7 +3513,7 @@ }, { "name" : "minecraft:totem_of_undying", - "id" : 558 + "id" : 564 }, { "name" : "minecraft:trapdoor", @@ -3349,7 +3525,7 @@ }, { "name" : "minecraft:trident", - "id" : 536 + "id" : 542 }, { "name" : "minecraft:tripwire", @@ -3369,7 +3545,7 @@ }, { "name" : "minecraft:tropical_fish_spawn_egg", - "id" : 477 + "id" : 480 }, { "name" : "minecraft:turtle_egg", @@ -3377,11 +3553,11 @@ }, { "name" : "minecraft:turtle_helmet", - "id" : 563 + "id" : 569 }, { "name" : "minecraft:turtle_spawn_egg", - "id" : 483 + "id" : 486 }, { "name" : "minecraft:twisting_vines", @@ -3413,15 +3589,15 @@ }, { "name" : "minecraft:vex_spawn_egg", - "id" : 474 + "id" : 477 }, { "name" : "minecraft:villager_spawn_egg", - "id" : 447 + "id" : 450 }, { "name" : "minecraft:vindicator_spawn_egg", - "id" : 472 + "id" : 475 }, { "name" : "minecraft:vine", @@ -3437,7 +3613,7 @@ }, { "name" : "minecraft:wandering_trader_spawn_egg", - "id" : 490 + "id" : 493 }, { "name" : "minecraft:warped_button", @@ -3445,7 +3621,7 @@ }, { "name" : "minecraft:warped_door", - "id" : 605 + "id" : 611 }, { "name" : "minecraft:warped_double_slab", @@ -3465,7 +3641,7 @@ }, { "name" : "minecraft:warped_fungus_on_a_stick", - "id" : 606 + "id" : 612 }, { "name" : "minecraft:warped_hyphae", @@ -3489,7 +3665,7 @@ }, { "name" : "minecraft:warped_sign", - "id" : 603 + "id" : 609 }, { "name" : "minecraft:warped_slab", @@ -3531,6 +3707,86 @@ "name" : "minecraft:waterlily", "id" : 111 }, + { + "name" : "minecraft:waxed_copper", + "id" : -344 + }, + { + "name" : "minecraft:waxed_cut_copper", + "id" : -351 + }, + { + "name" : "minecraft:waxed_cut_copper_slab", + "id" : -365 + }, + { + "name" : "minecraft:waxed_cut_copper_stairs", + "id" : -358 + }, + { + "name" : "minecraft:waxed_double_cut_copper_slab", + "id" : -372 + }, + { + "name" : "minecraft:waxed_exposed_copper", + "id" : -345 + }, + { + "name" : "minecraft:waxed_exposed_cut_copper", + "id" : -352 + }, + { + "name" : "minecraft:waxed_exposed_cut_copper_slab", + "id" : -366 + }, + { + "name" : "minecraft:waxed_exposed_cut_copper_stairs", + "id" : -359 + }, + { + "name" : "minecraft:waxed_exposed_double_cut_copper_slab", + "id" : -373 + }, + { + "name" : "minecraft:waxed_weathered_copper", + "id" : -346 + }, + { + "name" : "minecraft:waxed_weathered_cut_copper", + "id" : -353 + }, + { + "name" : "minecraft:waxed_weathered_cut_copper_slab", + "id" : -367 + }, + { + "name" : "minecraft:waxed_weathered_cut_copper_stairs", + "id" : -360 + }, + { + "name" : "minecraft:waxed_weathered_double_cut_copper_slab", + "id" : -374 + }, + { + "name" : "minecraft:weathered_copper", + "id" : -342 + }, + { + "name" : "minecraft:weathered_cut_copper", + "id" : -349 + }, + { + "name" : "minecraft:weathered_cut_copper_slab", + "id" : -363 + }, + { + "name" : "minecraft:weathered_cut_copper_stairs", + "id" : -356 + }, + { + "name" : "minecraft:weathered_double_cut_copper_slab", + "id" : -370 + }, { "name" : "minecraft:web", "id" : 30 @@ -3549,7 +3805,7 @@ }, { "name" : "minecraft:white_dye", - "id" : 408 + "id" : 411 }, { "name" : "minecraft:white_glazed_terracotta", @@ -3557,7 +3813,7 @@ }, { "name" : "minecraft:witch_spawn_egg", - "id" : 450 + "id" : 453 }, { "name" : "minecraft:wither_rose", @@ -3565,11 +3821,11 @@ }, { "name" : "minecraft:wither_skeleton_spawn_egg", - "id" : 462 + "id" : 465 }, { "name" : "minecraft:wolf_spawn_egg", - "id" : 437 + "id" : 440 }, { "name" : "minecraft:wood", @@ -3617,15 +3873,15 @@ }, { "name" : "minecraft:writable_book", - "id" : 500 + "id" : 506 }, { "name" : "minecraft:written_book", - "id" : 501 + "id" : 507 }, { "name" : "minecraft:yellow_dye", - "id" : 404 + "id" : 407 }, { "name" : "minecraft:yellow_flower", @@ -3637,22 +3893,22 @@ }, { "name" : "minecraft:zoglin_spawn_egg", - "id" : 496 + "id" : 499 }, { "name" : "minecraft:zombie_horse_spawn_egg", - "id" : 466 + "id" : 469 }, { "name" : "minecraft:zombie_pigman_spawn_egg", - "id" : 446 + "id" : 449 }, { "name" : "minecraft:zombie_spawn_egg", - "id" : 445 + "id" : 448 }, { "name" : "minecraft:zombie_villager_spawn_egg", - "id" : 475 + "id" : 478 } ] \ No newline at end of file From b5307ab3ede71c2c920122be77abe3cf4444ee3f Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 19 May 2021 22:24:11 -0400 Subject: [PATCH 045/107] 21w20a support --- README.md | 4 +- .../platform/spigot/GeyserSpigotPlugin.java | 11 +- .../manager/GeyserSpigot1_12WorldManager.java | 26 +-- .../GeyserSpigotFallbackWorldManager.java | 9 +- .../GeyserSpigotLegacyNativeWorldManager.java | 4 +- .../GeyserSpigotNativeWorldManager.java | 4 +- .../manager/GeyserSpigotWorldManager.java | 102 +----------- .../spigot/src/main/resources/biomes.json | 155 ------------------ connector/pom.xml | 2 +- .../connector/entity/AbstractArrowEntity.java | 2 +- .../entity/AreaEffectCloudEntity.java | 6 +- .../geysermc/connector/entity/BoatEntity.java | 14 +- .../entity/CommandBlockMinecartEntity.java | 4 +- .../entity/DefaultBlockMinecartEntity.java | 6 +- .../connector/entity/EnderCrystalEntity.java | 4 +- .../org/geysermc/connector/entity/Entity.java | 4 + .../connector/entity/ExpOrbEntity.java | 2 +- .../connector/entity/FireworkEntity.java | 4 +- .../connector/entity/FishingHookEntity.java | 2 +- .../entity/FurnaceMinecartEntity.java | 2 +- .../geysermc/connector/entity/ItemEntity.java | 2 +- .../connector/entity/ItemFrameEntity.java | 6 +- .../connector/entity/LivingEntity.java | 10 +- .../connector/entity/MinecartEntity.java | 12 +- .../geysermc/connector/entity/TNTEntity.java | 2 +- .../connector/entity/ThrowableEntity.java | 6 +- .../connector/entity/ThrownPotionEntity.java | 2 +- .../connector/entity/TippedArrowEntity.java | 6 +- .../connector/entity/TridentEntity.java | 2 +- .../connector/entity/WitherSkullEntity.java | 2 +- .../entity/living/AgeableEntity.java | 2 +- .../entity/living/ArmorStandEntity.java | 14 +- .../connector/entity/living/BatEntity.java | 2 +- .../entity/living/InsentientEntity.java | 2 +- .../entity/living/IronGolemEntity.java | 2 +- .../connector/entity/living/SlimeEntity.java | 2 +- .../entity/living/SnowGolemEntity.java | 2 +- .../entity/living/animal/BeeEntity.java | 4 +- .../entity/living/animal/FoxEntity.java | 6 +- .../entity/living/animal/HoglinEntity.java | 2 +- .../entity/living/animal/MooshroomEntity.java | 2 +- .../entity/living/animal/OcelotEntity.java | 2 +- .../entity/living/animal/PandaEntity.java | 8 +- .../entity/living/animal/PigEntity.java | 3 +- .../entity/living/animal/PolarBearEntity.java | 2 +- .../living/animal/PufferFishEntity.java | 5 +- .../entity/living/animal/RabbitEntity.java | 4 +- .../entity/living/animal/SheepEntity.java | 2 +- .../entity/living/animal/StriderEntity.java | 4 +- .../living/animal/TropicalFishEntity.java | 34 +--- .../entity/living/animal/TurtleEntity.java | 4 +- .../animal/horse/AbstractHorseEntity.java | 4 +- .../animal/horse/ChestedHorseEntity.java | 2 +- .../living/animal/horse/HorseEntity.java | 2 +- .../living/animal/horse/LlamaEntity.java | 6 +- .../living/animal/tameable/CatEntity.java | 6 +- .../living/animal/tameable/ParrotEntity.java | 2 +- .../animal/tameable/TameableEntity.java | 4 +- .../living/animal/tameable/WolfEntity.java | 8 +- .../living/merchant/VillagerEntity.java | 2 +- .../monster/AbstractSkeletonEntity.java | 2 +- .../living/monster/BasePiglinEntity.java | 2 +- .../entity/living/monster/BlazeEntity.java | 2 +- .../entity/living/monster/CreeperEntity.java | 6 +- .../living/monster/EnderDragonEntity.java | 4 +- .../entity/living/monster/EndermanEntity.java | 6 +- .../entity/living/monster/GhastEntity.java | 2 +- .../entity/living/monster/GuardianEntity.java | 2 +- .../entity/living/monster/PiglinEntity.java | 6 +- .../entity/living/monster/ShulkerEntity.java | 17 +- .../entity/living/monster/SpiderEntity.java | 2 +- .../entity/living/monster/VexEntity.java | 2 +- .../entity/living/monster/WitherEntity.java | 10 +- .../entity/living/monster/ZoglinEntity.java | 2 +- .../entity/living/monster/ZombieEntity.java | 2 +- .../living/monster/ZombieVillagerEntity.java | 4 +- .../raid/SpellcasterIllagerEntity.java | 2 +- .../living/monster/raid/VindicatorEntity.java | 2 +- .../connector/entity/player/PlayerEntity.java | 12 +- .../network/session/cache/ChunkCache.java | 11 +- .../network/session/cache/TagCache.java | 62 ++++++- .../translators/java/JavaPingPacket.java | 42 +++++ .../translators/world/GeyserWorldManager.java | 35 ---- .../translators/world/WorldManager.java | 22 --- .../world/block/BlockTranslator.java | 9 +- .../geysermc/connector/utils/BlockUtils.java | 48 +++--- .../geysermc/connector/utils/ChunkUtils.java | 14 +- 87 files changed, 314 insertions(+), 577 deletions(-) delete mode 100644 bootstrap/spigot/src/main/resources/biomes.json create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/java/JavaPingPacket.java diff --git a/README.md b/README.md index a51c61f9f..e141b3263 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have joined us here! -### Currently supporting Minecraft Bedrock v1.16.100 - v1.16.220 and Minecraft Java v1.16.4 - v1.16.5. +### Currently supporting Minecraft Bedrock v1.16.220.52 and Minecraft Java 21w20a. ## Setting Up Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set up Geyser. @@ -39,6 +39,8 @@ Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set - Some Entity Flags - Structure block UI +Extended height features can be "supported", but require additional work. + ## What can't be fixed The following things cannot be fixed without changes to Bedrock. As of now, they are not fixable in Geyser. 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 671ad18cf..7a5aaaf47 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 @@ -150,11 +150,6 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { if (isLegacy) geyserLogger.debug("Legacy version of Minecraft (1.12.2 or older) detected; falling back to ViaVersion for block state retrieval."); - boolean use3dBiomes = isCompatible(Bukkit.getServer().getVersion(), "1.16.0"); - if (!use3dBiomes) { - geyserLogger.debug("Legacy version of Minecraft (1.15.2 or older) detected; not using 3D biomes."); - } - boolean isPre1_12 = !isCompatible(Bukkit.getServer().getVersion(), "1.12.0"); // Set if we need to use a different method for getting a player's locale SpigotCommandSender.setUseLegacyLocaleMethod(isPre1_12); @@ -170,11 +165,11 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { this.geyserWorldManager = new GeyserSpigot1_12NativeWorldManager(this); } else { // Post-1.13 - this.geyserWorldManager = new GeyserSpigotLegacyNativeWorldManager(this, use3dBiomes); + this.geyserWorldManager = new GeyserSpigotLegacyNativeWorldManager(this); } } else { // No ViaVersion - this.geyserWorldManager = new GeyserSpigotNativeWorldManager(this, use3dBiomes); + this.geyserWorldManager = new GeyserSpigotNativeWorldManager(this); } geyserLogger.debug("Using NMS adapter: " + this.geyserWorldManager.getClass() + ", " + nmsVersion); } catch (Exception e) { @@ -196,7 +191,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { this.geyserWorldManager = new GeyserSpigotFallbackWorldManager(this); } else { // Post-1.13 - this.geyserWorldManager = new GeyserSpigotWorldManager(this, use3dBiomes); + this.geyserWorldManager = new GeyserSpigotWorldManager(this); } geyserLogger.debug("Using default world manager: " + this.geyserWorldManager.getClass()); } diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12WorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12WorldManager.java index a28eef5b4..77ca4540f 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12WorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12WorldManager.java @@ -25,9 +25,7 @@ package org.geysermc.platform.spigot.world.manager; -import com.github.steveice10.mc.protocol.data.game.chunk.Chunk; import org.bukkit.Bukkit; -import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; @@ -63,7 +61,7 @@ public class GeyserSpigot1_12WorldManager extends GeyserSpigotWorldManager { private final List> protocolList; public GeyserSpigot1_12WorldManager(Plugin plugin) { - super(plugin, false); + super(plugin); this.mappingData1_12to1_13 = ProtocolRegistry.getProtocol(Protocol1_13To1_12_2.class).getMappingData(); this.protocolList = ProtocolRegistry.getProtocolPath(CLIENT_PROTOCOL_VERSION, ProtocolVersion.v1_13.getVersion()); @@ -117,28 +115,6 @@ public class GeyserSpigot1_12WorldManager extends GeyserSpigotWorldManager { return blockId; } - @SuppressWarnings("deprecation") - @Override - public void getBlocksInSection(GeyserSession session, int x, int y, int z, Chunk chunk) { - Player player = Bukkit.getPlayer(session.getPlayerEntity().getUsername()); - if (player == null) { - return; - } - World world = player.getWorld(); - // Get block entity storage - BlockStorage storage = Via.getManager().getConnection(player.getUniqueId()).get(BlockStorage.class); - for (int blockY = 0; blockY < 16; blockY++) { // Cache-friendly iteration order - for (int blockZ = 0; blockZ < 16; blockZ++) { - for (int blockX = 0; blockX < 16; blockX++) { - Block block = world.getBlockAt((x << 4) + blockX, (y << 4) + blockY, (z << 4) + blockZ); - // Black magic that gets the old block state ID - int blockId = (block.getType().getId() << 4) | (block.getData() & 0xF); - chunk.set(blockX, blockY, blockZ, getLegacyBlock(storage, blockId, (x << 4) + blockX, (y << 4) + blockY, (z << 4) + blockZ)); - } - } - } - } - @Override public boolean isLegacy() { return true; diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotFallbackWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotFallbackWorldManager.java index a9de94db5..8bd2c6628 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotFallbackWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotFallbackWorldManager.java @@ -25,7 +25,6 @@ package org.geysermc.platform.spigot.world.manager; -import com.github.steveice10.mc.protocol.data.game.chunk.Chunk; import org.bukkit.plugin.Plugin; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.block.BlockTranslator; @@ -37,8 +36,7 @@ import org.geysermc.connector.network.translators.world.block.BlockTranslator; */ public class GeyserSpigotFallbackWorldManager extends GeyserSpigotWorldManager { public GeyserSpigotFallbackWorldManager(Plugin plugin) { - // Since this is pre-1.13 (and thus pre-1.15), there will never be 3D biomes. - super(plugin, false); + super(plugin); } @Override @@ -46,11 +44,6 @@ public class GeyserSpigotFallbackWorldManager extends GeyserSpigotWorldManager { return BlockTranslator.JAVA_AIR_ID; } - @Override - public void getBlocksInSection(GeyserSession session, int x, int y, int z, Chunk chunk) { - // Do nothing, since we can't do anything with the chunk - } - @Override public boolean hasOwnChunkCache() { return false; diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotLegacyNativeWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotLegacyNativeWorldManager.java index 8f407de0a..e76778258 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotLegacyNativeWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotLegacyNativeWorldManager.java @@ -46,8 +46,8 @@ public class GeyserSpigotLegacyNativeWorldManager extends GeyserSpigotNativeWorl private final Int2IntMap oldToNewBlockId; - public GeyserSpigotLegacyNativeWorldManager(GeyserSpigotPlugin plugin, boolean use3dBiomes) { - super(plugin, use3dBiomes); + public GeyserSpigotLegacyNativeWorldManager(GeyserSpigotPlugin plugin) { + super(plugin); IntList allBlockStates = adapter.getAllBlockStates(); oldToNewBlockId = new Int2IntOpenHashMap(allBlockStates.size()); ProtocolVersion serverVersion = plugin.getServerProtocolVersion(); diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotNativeWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotNativeWorldManager.java index cc9d5bddc..7e0b9267b 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotNativeWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotNativeWorldManager.java @@ -36,8 +36,8 @@ import org.geysermc.geyser.adapters.spigot.SpigotWorldAdapter; public class GeyserSpigotNativeWorldManager extends GeyserSpigotWorldManager { protected final SpigotWorldAdapter adapter; - public GeyserSpigotNativeWorldManager(Plugin plugin, boolean use3dBiomes) { - super(plugin, use3dBiomes); + public GeyserSpigotNativeWorldManager(Plugin plugin) { + super(plugin); adapter = SpigotAdapters.getWorldAdapter(); } diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotWorldManager.java index ba61eeb72..41e2fd801 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotWorldManager.java @@ -25,18 +25,13 @@ package org.geysermc.platform.spigot.world.manager; -import com.fasterxml.jackson.databind.JsonNode; import com.github.steveice10.mc.protocol.MinecraftConstants; -import com.github.steveice10.mc.protocol.data.game.chunk.Chunk; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.nbt.NbtType; -import it.unimi.dsi.fastutil.ints.Int2IntMap; -import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import org.bukkit.Bukkit; import org.bukkit.World; -import org.bukkit.block.Biome; import org.bukkit.block.Block; import org.bukkit.block.Lectern; import org.bukkit.block.data.BlockData; @@ -44,17 +39,13 @@ import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.BookMeta; import org.bukkit.plugin.Plugin; -import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.inventory.translators.LecternInventoryTranslator; import org.geysermc.connector.network.translators.world.GeyserWorldManager; import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.utils.BlockEntityUtils; -import org.geysermc.connector.utils.FileUtils; import org.geysermc.connector.utils.GameRule; -import org.geysermc.connector.utils.LanguageUtils; -import java.io.InputStream; import java.util.ArrayList; import java.util.List; @@ -67,48 +58,10 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { */ protected static final int CLIENT_PROTOCOL_VERSION = MinecraftConstants.PROTOCOL_VERSION; - /** - * Whether the server is pre-1.16 and therefore does not support 3D biomes on an API level guaranteed. - */ - private final boolean use3dBiomes; - /** - * Stores a list of {@link Biome} ordinal numbers to Minecraft biome numeric IDs. - * - * Working with the Biome enum in Spigot poses two problems: - * 1: The Biome enum values change in both order and names over the years. - * 2: There is no way to get the Minecraft biome ID from the name itself with Spigot. - * To solve both of these problems, we store a JSON file of every Biome enum that has existed, - * along with its 1.16 biome number. - * - * The key is the Spigot Biome ordinal; the value is the Minecraft Java biome numerical ID - */ - private final Int2IntMap biomeToIdMap = new Int2IntOpenHashMap(Biome.values().length); - private final Plugin plugin; - public GeyserSpigotWorldManager(Plugin plugin, boolean use3dBiomes) { - this.use3dBiomes = use3dBiomes; + public GeyserSpigotWorldManager(Plugin plugin) { this.plugin = plugin; - - // Load the values into the biome-to-ID map - InputStream biomeStream = FileUtils.getResource("biomes.json"); - JsonNode biomes; - try { - biomes = GeyserConnector.JSON_MAPPER.readTree(biomeStream); - } catch (Exception e) { - throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.toolbox.fail.runtime_java"), e); - } - // Only load in the biomes that are present in this version of Minecraft - for (Biome enumBiome : Biome.values()) { - JsonNode biome = biomes.get(enumBiome.toString()); - if (biome != null) { - biomeToIdMap.put(enumBiome.ordinal(), biome.intValue()); - } else { - GeyserConnector.getInstance().getLogger().debug("No biome mapping found for " + enumBiome.toString() + - ", defaulting to 0"); - biomeToIdMap.put(enumBiome.ordinal(), 0); - } - } } @Override @@ -121,64 +74,11 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { return BlockTranslator.getJavaIdBlockMap().getOrDefault(world.getBlockAt(x, y, z).getBlockData().getAsString(), BlockTranslator.JAVA_AIR_ID); } - @Override - public void getBlocksInSection(GeyserSession session, int x, int y, int z, Chunk chunk) { - Player bukkitPlayer; - if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) { - return; - } - World world = bukkitPlayer.getWorld(); - for (int blockY = 0; blockY < 16; blockY++) { // Cache-friendly iteration order - for (int blockZ = 0; blockZ < 16; blockZ++) { - for (int blockX = 0; blockX < 16; blockX++) { - Block block = world.getBlockAt((x << 4) + blockX, (y << 4) + blockY, (z << 4) + blockZ); - int id = BlockTranslator.getJavaIdBlockMap().getOrDefault(block.getBlockData().getAsString(), BlockTranslator.JAVA_AIR_ID); - chunk.set(blockX, blockY, blockZ, id); - } - } - } - } - @Override public boolean hasOwnChunkCache() { return true; } - @Override - @SuppressWarnings("deprecation") - public int[] getBiomeDataAt(GeyserSession session, int x, int z) { - int[] biomeData = new int[1024]; - World world = Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld(); - int chunkX = x << 4; - int chunkZ = z << 4; - int chunkXmax = chunkX + 16; - int chunkZmax = chunkZ + 16; - // 3D biomes didn't exist until 1.15 - if (use3dBiomes) { - for (int localX = chunkX; localX < chunkXmax; localX += 4) { - for (int localY = 0; localY < 255; localY += + 4) { - for (int localZ = chunkZ; localZ < chunkZmax; localZ += 4) { - // Index is based on wiki.vg's index requirements - final int i = ((localY >> 2) & 63) << 4 | ((localZ >> 2) & 3) << 2 | ((localX >> 2) & 3); - biomeData[i] = biomeToIdMap.getOrDefault(world.getBiome(localX, localY, localZ).ordinal(), 0); - } - } - } - } else { - // Looks like the same code, but we're not checking the Y coordinate here - for (int localX = chunkX; localX < chunkXmax; localX += 4) { - for (int localY = 0; localY < 255; localY += + 4) { - for (int localZ = chunkZ; localZ < chunkZmax; localZ += 4) { - // Index is based on wiki.vg's index requirements - final int i = ((localY >> 2) & 63) << 4 | ((localZ >> 2) & 3) << 2 | ((localX >> 2) & 3); - biomeData[i] = biomeToIdMap.getOrDefault(world.getBiome(localX, localZ).ordinal(), 0); - } - } - } - } - return biomeData; - } - @Override public NbtMap getLecternDataAt(GeyserSession session, int x, int y, int z, boolean isChunkLoad) { // Run as a task to prevent async issues diff --git a/bootstrap/spigot/src/main/resources/biomes.json b/bootstrap/spigot/src/main/resources/biomes.json deleted file mode 100644 index 56520e914..000000000 --- a/bootstrap/spigot/src/main/resources/biomes.json +++ /dev/null @@ -1,155 +0,0 @@ -{ - "MUTATED_ICE_FLATS" : 140, - "MUTATED_TAIGA" : 133, - "SAVANNA_PLATEAU_MOUNTAINS" : 164, - "DEEP_WARM_OCEAN" : 47, - "REDWOOD_TAIGA_HILLS" : 33, - "THE_VOID" : 127, - "COLD_TAIGA_MOUNTAINS" : 158, - "BAMBOO_JUNGLE_HILLS" : 169, - "MOUNTAINS" : 3, - "MESA_PLATEAU" : 39, - "SNOWY_TAIGA_HILLS" : 31, - "DEEP_FROZEN_OCEAN" : 50, - "EXTREME_HILLS" : 3, - "BIRCH_FOREST_MOUNTAINS" : 155, - "FOREST" : 4, - "BIRCH_FOREST" : 27, - "SNOWY_TUNDRA" : 12, - "ICE_SPIKES" : 140, - "FROZEN_OCEAN" : 10, - "WARPED_FOREST" : 172, - "WOODED_BADLANDS_PLATEAU" : 38, - "BADLANDS_PLATEAU" : 39, - "ICE_PLAINS_SPIKES" : 140, - "MEGA_TAIGA" : 32, - "MUTATED_SAVANNA_ROCK" : 164, - "SAVANNA_PLATEAU" : 36, - "DARK_FOREST_HILLS" : 157, - "END_MIDLANDS" : 41, - "SHATTERED_SAVANNA_PLATEAU" : 164, - "SAVANNA" : 35, - "MUSHROOM_ISLAND_SHORE" : 15, - "SWAMP" : 6, - "ICE_MOUNTAINS" : 13, - "BEACH" : 16, - "MUTATED_MESA_CLEAR_ROCK" : 167, - "END_HIGHLANDS" : 42, - "COLD_BEACH" : 26, - "JUNGLE" : 21, - "MUTATED_TAIGA_COLD" : 158, - "TALL_BIRCH_HILLS" : 156, - "DARK_FOREST" : 29, - "WOODED_HILLS" : 18, - "HELL" : 8, - "MUTATED_REDWOOD_TAIGA" : 160, - "MESA_PLATEAU_FOREST" : 38, - "MUSHROOM_ISLAND" : 14, - "BADLANDS" : 37, - "END_BARRENS" : 43, - "MUTATED_EXTREME_HILLS_WITH_TREES" : 162, - "MUTATED_JUNGLE_EDGE" : 151, - "MODIFIED_BADLANDS_PLATEAU" : 167, - "ROOFED_FOREST_MOUNTAINS" : 157, - "SOUL_SAND_VALLEY" : 170, - "DESERT" : 2, - "MUTATED_PLAINS" : 129, - "MUTATED_BIRCH_FOREST" : 155, - "WOODED_MOUNTAINS" : 34, - "TAIGA_HILLS" : 19, - "BAMBOO_JUNGLE" : 168, - "SWAMPLAND_MOUNTAINS" : 134, - "DESERT_MOUNTAINS" : 130, - "REDWOOD_TAIGA" : 32, - "MUSHROOM_FIELDS" : 14, - "GIANT_TREE_TAIGA_HILLS" : 33, - "PLAINS" : 1, - "JUNGLE_EDGE" : 23, - "SAVANNA_MOUNTAINS" : 163, - "DEEP_COLD_OCEAN" : 49, - "DESERT_LAKES" : 130, - "MOUNTAIN_EDGE" : 20, - "SNOWY_MOUNTAINS" : 13, - "MESA_PLATEAU_MOUNTAINS" : 167, - "JUNGLE_MOUNTAINS" : 149, - "SMALLER_EXTREME_HILLS" : 20, - "MESA_PLATEAU_FOREST_MOUNTAINS" : 166, - "NETHER_WASTES" : 8, - "BIRCH_FOREST_HILLS_MOUNTAINS" : 156, - "MUTATED_JUNGLE" : 149, - "WARM_OCEAN" : 44, - "DEEP_OCEAN" : 24, - "STONE_BEACH" : 25, - "MODIFIED_JUNGLE" : 149, - "MUTATED_SAVANNA" : 163, - "TAIGA_COLD_HILLS" : 31, - "OCEAN" : 0, - "SMALL_END_ISLANDS" : 40, - "MUSHROOM_FIELD_SHORE" : 15, - "GRAVELLY_MOUNTAINS" : 131, - "FROZEN_RIVER" : 11, - "TAIGA_COLD" : 30, - "BASALT_DELTAS" : 173, - "EXTREME_HILLS_WITH_TREES" : 34, - "MEGA_TAIGA_HILLS" : 33, - "MUTATED_FOREST" : 132, - "MUTATED_BIRCH_FOREST_HILLS" : 156, - "SKY" : 9, - "LUKEWARM_OCEAN" : 45, - "EXTREME_HILLS_MOUNTAINS" : 131, - "COLD_TAIGA_HILLS" : 31, - "THE_END" : 9, - "SUNFLOWER_PLAINS" : 129, - "SAVANNA_ROCK" : 36, - "ERODED_BADLANDS" : 165, - "STONE_SHORE" : 25, - "EXTREME_HILLS_PLUS_MOUNTAINS" : 162, - "CRIMSON_FOREST" : 171, - "VOID" : 127, - "SNOWY_TAIGA" : 30, - "SNOWY_TAIGA_MOUNTAINS" : 158, - "FLOWER_FOREST" : 132, - "COLD_OCEAN" : 46, - "BEACHES" : 16, - "MESA" : 37, - "MUSHROOM_SHORE" : 15, - "MESA_CLEAR_ROCK" : 39, - "NETHER" : 8, - "ICE_PLAINS" : 12, - "SHATTERED_SAVANNA" : 163, - "ROOFED_FOREST" : 29, - "GIANT_SPRUCE_TAIGA_HILLS" : 161, - "SNOWY_BEACH" : 26, - "MESA_BRYCE" : 165, - "JUNGLE_EDGE_MOUNTAINS" : 151, - "MUTATED_DESERT" : 130, - "MODIFIED_GRAVELLY_MOUNTAINS" : 158, - "MEGA_SPRUCE_TAIGA" : 160, - "TAIGA_MOUNTAINS" : 133, - "SMALL_MOUNTAINS" : 20, - "EXTREME_HILLS_PLUS" : 34, - "GIANT_SPRUCE_TAIGA" : 160, - "FOREST_HILLS" : 18, - "DESERT_HILLS" : 17, - "MUTATED_REDWOOD_TAIGA_HILLS" : 161, - "MEGA_SPRUCE_TAIGA_HILLS" : 161, - "RIVER" : 7, - "GIANT_TREE_TAIGA" : 32, - "SWAMPLAND" : 6, - "JUNGLE_HILLS" : 22, - "TALL_BIRCH_FOREST" : 155, - "DEEP_LUKEWARM_OCEAN" : 48, - "MESA_ROCK" : 38, - "SWAMP_HILLS" : 134, - "MODIFIED_WOODED_BADLANDS_PLATEAU" : 166, - "MODIFIED_JUNGLE_EDGE" : 151, - "BIRCH_FOREST_HILLS" : 28, - "COLD_TAIGA" : 30, - "TAIGA" : 5, - "MUTATED_MESA_ROCK" : 166, - "MUTATED_SWAMPLAND" : 134, - "ICE_FLATS" : 12, - "MUTATED_ROOFED_FOREST" : 157, - "MUTATED_MESA" : 165, - "MUTATED_EXTREME_HILLS" : 131 -} diff --git a/connector/pom.xml b/connector/pom.xml index 2db522092..e4b5adee4 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -122,7 +122,7 @@ com.github.steveice10 mcprotocollib - 21w17a-SNAPSHOT + 21w20a-SNAPSHOT compile diff --git a/connector/src/main/java/org/geysermc/connector/entity/AbstractArrowEntity.java b/connector/src/main/java/org/geysermc/connector/entity/AbstractArrowEntity.java index 25e8d37a1..54bec7257 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/AbstractArrowEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/AbstractArrowEntity.java @@ -44,7 +44,7 @@ public class AbstractArrowEntity extends Entity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 7) { + if (entityMetadata.getId() == 8) { byte data = (byte) entityMetadata.getValue(); metadata.getFlags().setFlag(EntityFlag.CRITICAL, (data & 0x01) == 0x01); diff --git a/connector/src/main/java/org/geysermc/connector/entity/AreaEffectCloudEntity.java b/connector/src/main/java/org/geysermc/connector/entity/AreaEffectCloudEntity.java index 6f3d0204f..cfd29fd8b 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/AreaEffectCloudEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/AreaEffectCloudEntity.java @@ -52,12 +52,12 @@ public class AreaEffectCloudEntity extends Entity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 7) { + if (entityMetadata.getId() == 8) { metadata.put(EntityData.AREA_EFFECT_CLOUD_RADIUS, entityMetadata.getValue()); metadata.put(EntityData.BOUNDING_BOX_WIDTH, 2.0f * (float) entityMetadata.getValue()); - } else if (entityMetadata.getId() == 8) { + } else if (entityMetadata.getId() == 9) { metadata.put(EntityData.EFFECT_COLOR, entityMetadata.getValue()); - } else if (entityMetadata.getId() == 10) { + } else if (entityMetadata.getId() == 11) { Particle particle = (Particle) entityMetadata.getValue(); int particleId = EffectRegistry.getParticleId(particle.getType()); if (particleId != -1) { diff --git a/connector/src/main/java/org/geysermc/connector/entity/BoatEntity.java b/connector/src/main/java/org/geysermc/connector/entity/BoatEntity.java index d07ecc965..a933b61b8 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/BoatEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/BoatEntity.java @@ -87,24 +87,24 @@ public class BoatEntity extends Entity { public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { // Time since last hit - if (entityMetadata.getId() == 7) { + if (entityMetadata.getId() == 8) { metadata.put(EntityData.HURT_TIME, entityMetadata.getValue()); } // Rocking direction - if (entityMetadata.getId() == 8) { + if (entityMetadata.getId() == 9) { metadata.put(EntityData.HURT_DIRECTION, entityMetadata.getValue()); } // 'Health' in Bedrock, damage taken in Java - if (entityMetadata.getId() == 9) { + if (entityMetadata.getId() == 10) { // Not exactly health but it makes motion in Bedrock metadata.put(EntityData.HEALTH, 40 - ((int) (float) entityMetadata.getValue())); } - if (entityMetadata.getId() == 10) { + if (entityMetadata.getId() == 11) { metadata.put(EntityData.VARIANT, entityMetadata.getValue()); - } else if (entityMetadata.getId() == 11) { + } else if (entityMetadata.getId() == 12) { isPaddlingLeft = (boolean) entityMetadata.getValue(); if (isPaddlingLeft) { // Java sends simply "true" and "false" (is_paddling_left), Bedrock keeps sending packets as you're rowing @@ -124,7 +124,7 @@ public class BoatEntity extends Entity { metadata.put(EntityData.ROW_TIME_LEFT, 0.0f); } } - else if (entityMetadata.getId() == 12) { + else if (entityMetadata.getId() == 13) { isPaddlingRight = (boolean) entityMetadata.getValue(); if (isPaddlingRight) { paddleTimeRight = 0f; @@ -139,7 +139,7 @@ public class BoatEntity extends Entity { } else { metadata.put(EntityData.ROW_TIME_RIGHT, 0.0f); } - } else if (entityMetadata.getId() == 13) { + } else if (entityMetadata.getId() == 14) { // Possibly - I don't think this does anything? metadata.put(EntityData.BOAT_BUBBLE_TIME, entityMetadata.getValue()); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/CommandBlockMinecartEntity.java b/connector/src/main/java/org/geysermc/connector/entity/CommandBlockMinecartEntity.java index 52183c431..bda4e7972 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/CommandBlockMinecartEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/CommandBlockMinecartEntity.java @@ -46,10 +46,10 @@ public class CommandBlockMinecartEntity extends DefaultBlockMinecartEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 13) { + if (entityMetadata.getId() == 14) { metadata.put(EntityData.COMMAND_BLOCK_COMMAND, entityMetadata.getValue()); } - if (entityMetadata.getId() == 14) { + if (entityMetadata.getId() == 15) { metadata.put(EntityData.COMMAND_BLOCK_LAST_OUTPUT, MessageTranslator.convertMessage((Component) entityMetadata.getValue())); } super.updateBedrockMetadata(entityMetadata, session); diff --git a/connector/src/main/java/org/geysermc/connector/entity/DefaultBlockMinecartEntity.java b/connector/src/main/java/org/geysermc/connector/entity/DefaultBlockMinecartEntity.java index 805105c64..24e5bb259 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/DefaultBlockMinecartEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/DefaultBlockMinecartEntity.java @@ -56,7 +56,7 @@ public class DefaultBlockMinecartEntity extends MinecartEntity { public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { // Custom block - if (entityMetadata.getId() == 10) { + if (entityMetadata.getId() == 11) { customBlock = (int) entityMetadata.getValue(); if (showCustomBlock) { @@ -65,7 +65,7 @@ public class DefaultBlockMinecartEntity extends MinecartEntity { } // Custom block offset - if (entityMetadata.getId() == 11) { + if (entityMetadata.getId() == 12) { customBlockOffset = (int) entityMetadata.getValue(); if (showCustomBlock) { @@ -74,7 +74,7 @@ public class DefaultBlockMinecartEntity extends MinecartEntity { } // If the custom block should be enabled - if (entityMetadata.getId() == 12) { + if (entityMetadata.getId() == 13) { if ((boolean) entityMetadata.getValue()) { showCustomBlock = true; metadata.put(EntityData.DISPLAY_ITEM, session.getBlockTranslator().getBedrockBlockId(customBlock)); diff --git a/connector/src/main/java/org/geysermc/connector/entity/EnderCrystalEntity.java b/connector/src/main/java/org/geysermc/connector/entity/EnderCrystalEntity.java index 8997512d9..596ccf089 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/EnderCrystalEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/EnderCrystalEntity.java @@ -47,7 +47,7 @@ public class EnderCrystalEntity extends Entity { public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { // Show beam // Usually performed client-side on Bedrock except for Ender Dragon respawn event - if (entityMetadata.getId() == 7) { + if (entityMetadata.getId() == 8) { if (entityMetadata.getValue() instanceof Position) { Position pos = (Position) entityMetadata.getValue(); metadata.put(EntityData.BLOCK_TARGET, Vector3i.from(pos.getX(), pos.getY(), pos.getZ())); @@ -56,7 +56,7 @@ public class EnderCrystalEntity extends Entity { } } // There is a base located on the ender crystal - if (entityMetadata.getId() == 8) { + if (entityMetadata.getId() == 9) { metadata.getFlags().setFlag(EntityFlag.SHOW_BOTTOM, (boolean) entityMetadata.getValue()); } super.updateBedrockMetadata(entityMetadata, session); diff --git a/connector/src/main/java/org/geysermc/connector/entity/Entity.java b/connector/src/main/java/org/geysermc/connector/entity/Entity.java index bc371690b..bdd01ee4e 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/Entity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/Entity.java @@ -329,6 +329,10 @@ public class Entity { metadata.put(EntityData.BOUNDING_BOX_WIDTH, width); metadata.put(EntityData.BOUNDING_BOX_HEIGHT, height); break; + case 7: + //TODO check + metadata.put(EntityData.FREEZING_EFFECT_STRENGTH, entityMetadata.getValue()); + break; } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/ExpOrbEntity.java b/connector/src/main/java/org/geysermc/connector/entity/ExpOrbEntity.java index 45595f504..f6fbcde9d 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ExpOrbEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/ExpOrbEntity.java @@ -32,7 +32,7 @@ import org.geysermc.connector.network.session.GeyserSession; public class ExpOrbEntity extends Entity { - private int amount; + private final int amount; public ExpOrbEntity(int amount, 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/FireworkEntity.java b/connector/src/main/java/org/geysermc/connector/entity/FireworkEntity.java index deaf3fadf..6ae0648b7 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/FireworkEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/FireworkEntity.java @@ -55,7 +55,7 @@ public class FireworkEntity extends Entity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 7) { + if (entityMetadata.getId() == 8) { ItemStack item = (ItemStack) entityMetadata.getValue(); if (item == null) { return; @@ -134,7 +134,7 @@ public class FireworkEntity extends Entity { NbtMapBuilder builder = NbtMap.builder(); builder.put("Fireworks", fireworksBuilder.build()); metadata.put(EntityData.DISPLAY_ITEM, builder.build()); - } else if (entityMetadata.getId() == 8 && !entityMetadata.getValue().equals(OptionalInt.empty()) && ((OptionalInt) entityMetadata.getValue()).getAsInt() == session.getPlayerEntity().getEntityId()) { + } else if (entityMetadata.getId() == 9 && !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(); float yaw = entity.getRotation().getX(); 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 0738c3819..4f261ef39 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/FishingHookEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/FishingHookEntity.java @@ -67,7 +67,7 @@ public class FishingHookEntity extends ThrowableEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 7) { // Hooked entity + if (entityMetadata.getId() == 8) { // Hooked entity int hookedEntityId = (int) entityMetadata.getValue() - 1; Entity entity = session.getEntityCache().getEntityByJavaId(hookedEntityId); if (entity == null && session.getPlayerEntity().getEntityId() == hookedEntityId) { diff --git a/connector/src/main/java/org/geysermc/connector/entity/FurnaceMinecartEntity.java b/connector/src/main/java/org/geysermc/connector/entity/FurnaceMinecartEntity.java index fdf24f176..2eb083883 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/FurnaceMinecartEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/FurnaceMinecartEntity.java @@ -42,7 +42,7 @@ public class FurnaceMinecartEntity extends DefaultBlockMinecartEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 13 && !showCustomBlock) { + if (entityMetadata.getId() == 14 && !showCustomBlock) { hasFuel = (boolean) entityMetadata.getValue(); updateDefaultBlockMetadata(session); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/ItemEntity.java b/connector/src/main/java/org/geysermc/connector/entity/ItemEntity.java index ed48e2670..ad269e41b 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ItemEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/ItemEntity.java @@ -54,7 +54,7 @@ public class ItemEntity extends Entity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 7) { + if (entityMetadata.getId() == 8) { AddItemEntityPacket itemPacket = new AddItemEntityPacket(); itemPacket.setRuntimeEntityId(geyserId); itemPacket.setPosition(position.add(0d, this.entityType.getOffset(), 0d)); 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 79711b0cb..ead2b3b3d 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java @@ -105,7 +105,7 @@ public class ItemFrameEntity extends Entity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 7 && entityMetadata.getValue() != null) { + if (entityMetadata.getId() == 8 && entityMetadata.getValue() != null) { this.heldItem = (ItemStack) entityMetadata.getValue(); ItemData itemData = ItemTranslator.translateToBedrock(session, heldItem); ItemEntry itemEntry = ItemRegistry.getItem((ItemStack) entityMetadata.getValue()); @@ -124,11 +124,11 @@ public class ItemFrameEntity extends Entity { cachedTag = tag.build(); updateBlock(session); } - else if (entityMetadata.getId() == 7 && entityMetadata.getValue() == null && cachedTag != null) { + else if (entityMetadata.getId() == 8 && entityMetadata.getValue() == null && cachedTag != null) { cachedTag = getDefaultTag(); updateBlock(session); } - else if (entityMetadata.getId() == 8) { + else if (entityMetadata.getId() == 9) { rotation = ((int) entityMetadata.getValue()) * 45; if (cachedTag == null) { updateBlock(session); 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 025cf085b..f3e5661fe 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/LivingEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/LivingEntity.java @@ -68,7 +68,7 @@ public class LivingEntity extends Entity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { switch (entityMetadata.getId()) { - case 7: // blocking + case 8: // blocking byte xd = (byte) entityMetadata.getValue(); //blocking gets triggered when using a bow, but if we set USING_ITEM for all items, it may look like @@ -81,16 +81,16 @@ public class LivingEntity extends Entity { // Riptide spin attack metadata.getFlags().setFlag(EntityFlag.DAMAGE_NEARBY_MOBS, (xd & 0x04) == 0x04); break; - case 8: + case 9: metadata.put(EntityData.HEALTH, entityMetadata.getValue()); break; - case 9: + case 10: metadata.put(EntityData.EFFECT_COLOR, entityMetadata.getValue()); break; - case 10: + case 11: metadata.put(EntityData.EFFECT_AMBIENT, (byte) ((boolean) entityMetadata.getValue() ? 1 : 0)); break; - case 13: // Bed Position + case 14: // Bed Position Position bedPosition = (Position) entityMetadata.getValue(); if (bedPosition != null) { metadata.put(EntityData.BED_POSITION, Vector3i.from(bedPosition.getX(), bedPosition.getY(), bedPosition.getZ())); diff --git a/connector/src/main/java/org/geysermc/connector/entity/MinecartEntity.java b/connector/src/main/java/org/geysermc/connector/entity/MinecartEntity.java index b66b049eb..18d1d92a1 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/MinecartEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/MinecartEntity.java @@ -40,33 +40,33 @@ public class MinecartEntity extends Entity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 7) { + if (entityMetadata.getId() == 8) { metadata.put(EntityData.HEALTH, entityMetadata.getValue()); } // Direction in which the minecart is shaking - if (entityMetadata.getId() == 8) { + if (entityMetadata.getId() == 9) { metadata.put(EntityData.HURT_DIRECTION, entityMetadata.getValue()); } // Power in Java, time in Bedrock - if (entityMetadata.getId() == 9) { + if (entityMetadata.getId() == 10) { metadata.put(EntityData.HURT_TIME, Math.min((int) (float) entityMetadata.getValue(), 15)); } if (!(this instanceof DefaultBlockMinecartEntity)) { // Handled in the DefaultBlockMinecartEntity class // Custom block - if (entityMetadata.getId() == 10) { + if (entityMetadata.getId() == 11) { metadata.put(EntityData.DISPLAY_ITEM, session.getBlockTranslator().getBedrockBlockId((int) entityMetadata.getValue())); } // Custom block offset - if (entityMetadata.getId() == 11) { + if (entityMetadata.getId() == 12) { metadata.put(EntityData.DISPLAY_OFFSET, entityMetadata.getValue()); } // If the custom block should be enabled - if (entityMetadata.getId() == 12) { + if (entityMetadata.getId() == 13) { // Needs a byte based off of Java's boolean metadata.put(EntityData.CUSTOM_DISPLAY, (byte) ((boolean) entityMetadata.getValue() ? 1 : 0)); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/TNTEntity.java b/connector/src/main/java/org/geysermc/connector/entity/TNTEntity.java index ad73c1d9c..1d70d9d5f 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/TNTEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/TNTEntity.java @@ -45,7 +45,7 @@ public class TNTEntity extends Entity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 7) { + if (entityMetadata.getId() == 8) { currentTick = (int) entityMetadata.getValue(); metadata.getFlags().setFlag(EntityFlag.IGNITED, true); metadata.put(EntityData.FUSE_LENGTH, currentTick); diff --git a/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java b/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java index 1088b2a0b..9aea49656 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java @@ -167,10 +167,8 @@ public class ThrowableEntity extends Entity implements Tickable { */ protected boolean isInWater(GeyserSession session) { if (session.getConnector().getConfig().isCacheChunks()) { - if (0 <= position.getFloorY() && position.getFloorY() <= 255) { - int block = session.getConnector().getWorldManager().getBlockAt(session, position.toInt()); - return BlockStateValues.getWaterLevel(block) != -1; - } + int block = session.getConnector().getWorldManager().getBlockAt(session, position.toInt()); + return BlockStateValues.getWaterLevel(block) != -1; } return false; } diff --git a/connector/src/main/java/org/geysermc/connector/entity/ThrownPotionEntity.java b/connector/src/main/java/org/geysermc/connector/entity/ThrownPotionEntity.java index c1f82836b..9ce218a81 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ThrownPotionEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/ThrownPotionEntity.java @@ -51,7 +51,7 @@ public class ThrownPotionEntity extends ThrowableEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 7 && entityMetadata.getType() == MetadataType.ITEM) { + if (entityMetadata.getId() == 8 && entityMetadata.getType() == MetadataType.ITEM) { ItemStack itemStack = (ItemStack) entityMetadata.getValue(); ItemEntry itemEntry = ItemRegistry.getItem(itemStack); if (itemEntry.getJavaIdentifier().endsWith("potion") && itemStack.getNbt() != null) { diff --git a/connector/src/main/java/org/geysermc/connector/entity/TippedArrowEntity.java b/connector/src/main/java/org/geysermc/connector/entity/TippedArrowEntity.java index 714a70daa..f7b63435f 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/TippedArrowEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/TippedArrowEntity.java @@ -44,17 +44,17 @@ public class TippedArrowEntity extends AbstractArrowEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { // Arrow potion effect color - if (entityMetadata.getId() == 9) { + if (entityMetadata.getId() == 10) { int potionColor = (int) entityMetadata.getValue(); // -1 means no color if (potionColor == -1) { - metadata.remove(EntityData.CUSTOM_DISPLAY); + metadata.put(EntityData.CUSTOM_DISPLAY, 0); } else { TippedArrowPotion potion = TippedArrowPotion.getByJavaColor(potionColor); if (potion != null && potion.getJavaColor() != -1) { metadata.put(EntityData.CUSTOM_DISPLAY, (byte) potion.getBedrockId()); } else { - metadata.remove(EntityData.CUSTOM_DISPLAY); + metadata.put(EntityData.CUSTOM_DISPLAY, 0); } } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/TridentEntity.java b/connector/src/main/java/org/geysermc/connector/entity/TridentEntity.java index 014c0049e..19ec27692 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/TridentEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/TridentEntity.java @@ -39,7 +39,7 @@ public class TridentEntity extends AbstractArrowEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 10) { + if (entityMetadata.getId() == 11) { metadata.getFlags().setFlag(EntityFlag.ENCHANTED, (boolean) entityMetadata.getValue()); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/WitherSkullEntity.java b/connector/src/main/java/org/geysermc/connector/entity/WitherSkullEntity.java index 3548e0dfe..6209538dc 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/WitherSkullEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/WitherSkullEntity.java @@ -46,7 +46,7 @@ public class WitherSkullEntity extends ItemedFireballEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 7) { + if (entityMetadata.getId() == 8) { boolean newIsCharged = (boolean) entityMetadata.getValue(); if (newIsCharged != isCharged) { isCharged = newIsCharged; diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/AgeableEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/AgeableEntity.java index 909cb736b..8f8166d07 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/AgeableEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/AgeableEntity.java @@ -40,7 +40,7 @@ public class AgeableEntity extends CreatureEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 15) { + if (entityMetadata.getId() == 16) { boolean isBaby = (boolean) entityMetadata.getValue(); metadata.put(EntityData.SCALE, isBaby ? .55f : 1f); metadata.getFlags().setFlag(EntityFlag.BABY, isBaby); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/ArmorStandEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/ArmorStandEntity.java index 13f8a4c1d..a616f9269 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/ArmorStandEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/ArmorStandEntity.java @@ -126,7 +126,7 @@ public class ArmorStandEntity extends LivingEntity { } } else if (entityMetadata.getId() == 2) { updateSecondEntityStatus(false); - } else if (entityMetadata.getId() == 14 && entityMetadata.getType() == MetadataType.BYTE) { + } else if (entityMetadata.getId() == 15 && entityMetadata.getType() == MetadataType.BYTE) { byte xd = (byte) entityMetadata.getValue(); // isSmall @@ -169,37 +169,37 @@ public class ArmorStandEntity extends LivingEntity { EntityFlag negativeYToggle = null; EntityFlag negativeZToggle = null; switch (entityMetadata.getId()) { - case 15: // Head + case 16: // Head dataLeech = EntityData.MARK_VARIANT; negativeXToggle = EntityFlag.INTERESTED; negativeYToggle = EntityFlag.CHARGED; negativeZToggle = EntityFlag.POWERED; break; - case 16: // Body + case 17: // Body dataLeech = EntityData.VARIANT; negativeXToggle = EntityFlag.IN_LOVE; negativeYToggle = EntityFlag.CELEBRATING; negativeZToggle = EntityFlag.CELEBRATING_SPECIAL; break; - case 17: // Left arm + case 18: // Left arm dataLeech = EntityData.TRADE_TIER; negativeXToggle = EntityFlag.CHARGING; negativeYToggle = EntityFlag.CRITICAL; negativeZToggle = EntityFlag.DANCING; break; - case 18: // Right arm + case 19: // Right arm dataLeech = EntityData.MAX_TRADE_TIER; negativeXToggle = EntityFlag.ELDER; negativeYToggle = EntityFlag.EMOTING; negativeZToggle = EntityFlag.IDLING; break; - case 19: // Left leg + case 20: // Left leg dataLeech = EntityData.SKIN_ID; negativeXToggle = EntityFlag.IS_ILLAGER_CAPTAIN; negativeYToggle = EntityFlag.IS_IN_UI; negativeZToggle = EntityFlag.LINGERING; break; - case 20: // Right leg + case 21: // Right leg dataLeech = EntityData.HURT_DIRECTION; negativeXToggle = EntityFlag.IS_PREGNANT; negativeYToggle = EntityFlag.SHEARED; diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/BatEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/BatEntity.java index 80b426b42..110c02e06 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/BatEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/BatEntity.java @@ -39,7 +39,7 @@ public class BatEntity extends AmbientEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 15) { + if (entityMetadata.getId() == 16) { byte xd = (byte) entityMetadata.getValue(); metadata.getFlags().setFlag(EntityFlag.RESTING, (xd & 0x01) == 0x01); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/InsentientEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/InsentientEntity.java index 90bb373fe..d0f99cdb9 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/InsentientEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/InsentientEntity.java @@ -41,7 +41,7 @@ public class InsentientEntity extends LivingEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 14 && entityMetadata.getType() == MetadataType.BYTE) { + if (entityMetadata.getId() == 15 && entityMetadata.getType() == MetadataType.BYTE) { byte xd = (byte) entityMetadata.getValue(); metadata.getFlags().setFlag(EntityFlag.NO_AI, (xd & 0x01) == 0x01); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/IronGolemEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/IronGolemEntity.java index 5fca355f5..f47ff2c7f 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/IronGolemEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/IronGolemEntity.java @@ -53,7 +53,7 @@ public class IronGolemEntity extends GolemEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { super.updateBedrockMetadata(entityMetadata, session); - if (entityMetadata.getId() == 8) { + if (entityMetadata.getId() == 9) { // Required so the resource pack sees the entity health attributes.put(AttributeType.HEALTH, AttributeType.HEALTH.getAttribute(metadata.getFloat(EntityData.HEALTH), 100f)); updateBedrockAttributes(session); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/SlimeEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/SlimeEntity.java index da088410a..f08fcc796 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/SlimeEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/SlimeEntity.java @@ -39,7 +39,7 @@ public class SlimeEntity extends InsentientEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 15) { + if (entityMetadata.getId() == 16) { this.metadata.put(EntityData.SCALE, 0.10f + (int) entityMetadata.getValue()); } super.updateBedrockMetadata(entityMetadata, session); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/SnowGolemEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/SnowGolemEntity.java index 144b0ed24..6bfb23564 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/SnowGolemEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/SnowGolemEntity.java @@ -39,7 +39,7 @@ public class SnowGolemEntity extends GolemEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 15) { + if (entityMetadata.getId() == 16) { byte xd = (byte) entityMetadata.getValue(); // Handle the visibility of the pumpkin metadata.getFlags().setFlag(EntityFlag.SHEARED, (xd & 0x10) != 0x10); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/BeeEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/BeeEntity.java index b2d5faaad..59035bb85 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/BeeEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/BeeEntity.java @@ -43,7 +43,7 @@ public class BeeEntity extends AnimalEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 16) { + if (entityMetadata.getId() == 17) { byte xd = (byte) entityMetadata.getValue(); // Bee is performing sting attack; trigger animation if ((xd & 0x02) == 0x02) { @@ -58,7 +58,7 @@ public class BeeEntity extends AnimalEntity { // If the bee has nectar or not metadata.getFlags().setFlag(EntityFlag.POWERED, (xd & 0x08) == 0x08); } - if (entityMetadata.getId() == 17) { + if (entityMetadata.getId() == 18) { // Converting "anger time" to a boolean metadata.getFlags().setFlag(EntityFlag.ANGRY, (int) entityMetadata.getValue() > 0); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/FoxEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/FoxEntity.java index ff71e87f0..2c1934139 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/FoxEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/FoxEntity.java @@ -41,10 +41,10 @@ public class FoxEntity extends AnimalEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 16) { + if (entityMetadata.getId() == 17) { metadata.put(EntityData.VARIANT, entityMetadata.getValue()); } - if (entityMetadata.getId() == 17) { + if (entityMetadata.getId() == 18) { byte xd = (byte) entityMetadata.getValue(); metadata.getFlags().setFlag(EntityFlag.SITTING, (xd & 0x01) == 0x01); metadata.getFlags().setFlag(EntityFlag.SNEAKING, (xd & 0x04) == 0x04); @@ -56,6 +56,6 @@ public class FoxEntity extends AnimalEntity { @Override public boolean canEat(GeyserSession session, String javaIdentifierStripped, ItemEntry itemEntry) { - return javaIdentifierStripped.equals("sweet_berries"); + return session.getTagCache().isFoxFood(itemEntry); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/HoglinEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/HoglinEntity.java index 9fbb17725..1534abb0b 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/HoglinEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/HoglinEntity.java @@ -41,7 +41,7 @@ public class HoglinEntity extends AnimalEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 16) { + if (entityMetadata.getId() == 17) { // Immune to zombification? // Apply shaking effect if not in the nether and zombification is possible metadata.getFlags().setFlag(EntityFlag.SHAKING, !((boolean) entityMetadata.getValue()) && !session.getDimension().equals(DimensionUtils.NETHER)); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/MooshroomEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/MooshroomEntity.java index 9e12f3f1e..e2e22c73d 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/MooshroomEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/MooshroomEntity.java @@ -39,7 +39,7 @@ public class MooshroomEntity extends AnimalEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 16) { + if (entityMetadata.getId() == 17) { metadata.put(EntityData.VARIANT, entityMetadata.getValue().equals("brown") ? 1 : 0); } super.updateBedrockMetadata(entityMetadata, session); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/OcelotEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/OcelotEntity.java index 1cf541c8c..48a5e08bf 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/OcelotEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/OcelotEntity.java @@ -40,7 +40,7 @@ public class OcelotEntity extends AnimalEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 16) { + if (entityMetadata.getId() == 17) { metadata.getFlags().setFlag(EntityFlag.TRUSTING, (boolean) entityMetadata.getValue()); } super.updateBedrockMetadata(entityMetadata, session); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/PandaEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/PandaEntity.java index 2f5ced080..83f0ed8d9 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/PandaEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/PandaEntity.java @@ -47,7 +47,7 @@ public class PandaEntity extends AnimalEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 18) { + if (entityMetadata.getId() == 19) { metadata.getFlags().setFlag(EntityFlag.EATING, (int) entityMetadata.getValue() > 0); metadata.put(EntityData.EATING_COUNTER, entityMetadata.getValue()); if ((int) entityMetadata.getValue() != 0) { @@ -59,15 +59,15 @@ public class PandaEntity extends AnimalEntity { session.sendUpstreamPacket(packet); } } - if (entityMetadata.getId() == 19) { + if (entityMetadata.getId() == 20) { mainGene = (int) (byte) entityMetadata.getValue(); updateAppearance(); } - if (entityMetadata.getId() == 20) { + if (entityMetadata.getId() == 21) { hiddenGene = (int) (byte) entityMetadata.getValue(); updateAppearance(); } - if (entityMetadata.getId() == 21) { + if (entityMetadata.getId() == 22) { byte xd = (byte) entityMetadata.getValue(); metadata.getFlags().setFlag(EntityFlag.SNEEZING, (xd & 0x02) == 0x02); metadata.getFlags().setFlag(EntityFlag.ROLLING, (xd & 0x04) == 0x04); 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 bbb1aed20..27d736b33 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 @@ -40,8 +40,7 @@ public class PigEntity extends AnimalEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - - if (entityMetadata.getId() == 16) { + if (entityMetadata.getId() == 17) { metadata.getFlags().setFlag(EntityFlag.SADDLED, (boolean) entityMetadata.getValue()); } super.updateBedrockMetadata(entityMetadata, session); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/PolarBearEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/PolarBearEntity.java index f33635aeb..8f292411d 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/PolarBearEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/PolarBearEntity.java @@ -40,7 +40,7 @@ public class PolarBearEntity extends AnimalEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 16) { + if (entityMetadata.getId() == 17) { metadata.getFlags().setFlag(EntityFlag.STANDING, (boolean) entityMetadata.getValue()); } super.updateBedrockMetadata(entityMetadata, session); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/PufferFishEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/PufferFishEntity.java index 9a6a712f9..66f81e42f 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/PufferFishEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/PufferFishEntity.java @@ -40,10 +40,11 @@ public class PufferFishEntity extends AbstractFishEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 16) { + if (entityMetadata.getId() == 17) { // Transfers correctly but doesn't apply on the client + //TODO check - probably because we didn't set PUFFERFISH_SIZE as a byte int puffsize = (int) entityMetadata.getValue(); - metadata.put(EntityData.PUFFERFISH_SIZE, puffsize); + metadata.put(EntityData.PUFFERFISH_SIZE, (byte) puffsize); metadata.put(EntityData.VARIANT, puffsize); } super.updateBedrockMetadata(entityMetadata, session); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/RabbitEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/RabbitEntity.java index 9a4691cc0..41579d6a6 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/RabbitEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/RabbitEntity.java @@ -42,14 +42,14 @@ public class RabbitEntity extends AnimalEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { super.updateBedrockMetadata(entityMetadata, session); - if (entityMetadata.getId() == 15) { + if (entityMetadata.getId() == 16) { metadata.put(EntityData.SCALE, .55f); boolean isBaby = (boolean) entityMetadata.getValue(); if (isBaby) { metadata.put(EntityData.SCALE, .35f); metadata.getFlags().setFlag(EntityFlag.BABY, true); } - } else if (entityMetadata.getId() == 16) { + } else if (entityMetadata.getId() == 17) { int variant = (int) entityMetadata.getValue(); // Change the killer bunny to display as white since it only exists on Java Edition diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/SheepEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/SheepEntity.java index 37bb2fdeb..f723eff2b 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/SheepEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/SheepEntity.java @@ -40,7 +40,7 @@ public class SheepEntity extends AnimalEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 16) { + if (entityMetadata.getId() == 17) { byte xd = (byte) entityMetadata.getValue(); metadata.getFlags().setFlag(EntityFlag.SHEARED, (xd & 0x10) == 0x10); metadata.put(EntityData.COLOR, xd); 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 a90a044bc..51b7aacad 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 @@ -46,10 +46,10 @@ public class StriderEntity extends AnimalEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 17) { + if (entityMetadata.getId() == 18) { shaking = (boolean) entityMetadata.getValue(); } - if (entityMetadata.getId() == 18) { + if (entityMetadata.getId() == 19) { metadata.getFlags().setFlag(EntityFlag.SADDLED, (boolean) entityMetadata.getValue()); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/TropicalFishEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/TropicalFishEntity.java index 8d5b476a0..7a5906dd9 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/TropicalFishEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/TropicalFishEntity.java @@ -28,8 +28,6 @@ package org.geysermc.connector.entity.living.animal; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import lombok.AllArgsConstructor; -import lombok.Getter; import org.geysermc.connector.entity.living.AbstractFishEntity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; @@ -42,34 +40,14 @@ public class TropicalFishEntity extends AbstractFishEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 16) { - TropicalFishVariant variant = TropicalFishVariant.fromVariantNumber((int) entityMetadata.getValue()); + if (entityMetadata.getId() == 17) { + int varNumber = (int) entityMetadata.getValue(); - metadata.put(EntityData.VARIANT, variant.getShape()); // Shape 0-1 - metadata.put(EntityData.MARK_VARIANT, variant.getPattern()); // Pattern 0-5 - metadata.put(EntityData.COLOR, variant.getBaseColor()); // Base color 0-15 - metadata.put(EntityData.COLOR_2, variant.getPatternColor()); // Pattern color 0-15 + metadata.put(EntityData.VARIANT, varNumber & 0xFF); // Shape 0-1 + metadata.put(EntityData.MARK_VARIANT, (varNumber >> 8) & 0xFF); // Pattern 0-5 + metadata.put(EntityData.COLOR, (byte) ((varNumber >> 16) & 0xFF)); // Base color 0-15 + metadata.put(EntityData.COLOR_2, (byte) ((varNumber >> 24) & 0xFF)); // Pattern color 0-15 } super.updateBedrockMetadata(entityMetadata, session); } - - @Getter - @AllArgsConstructor - private static class TropicalFishVariant { - private int shape; - private int pattern; - private byte baseColor; - private byte patternColor; - - /** - * Convert the variant number from Java into separate values - * - * @param varNumber Variant number from Java edition - * - * @return The variant converted into TropicalFishVariant - */ - public static TropicalFishVariant fromVariantNumber(int varNumber) { - return new TropicalFishVariant((varNumber & 0xFF), ((varNumber >> 8) & 0xFF), (byte) ((varNumber >> 16) & 0xFF), (byte) ((varNumber >> 24) & 0xFF)); - } - } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/TurtleEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/TurtleEntity.java index 536f40755..7e9e3260d 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/TurtleEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/TurtleEntity.java @@ -40,9 +40,9 @@ public class TurtleEntity extends AnimalEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 17) { + if (entityMetadata.getId() == 18) { metadata.getFlags().setFlag(EntityFlag.IS_PREGNANT, (boolean) entityMetadata.getValue()); - } else if (entityMetadata.getId() == 18) { + } else if (entityMetadata.getId() == 19) { metadata.getFlags().setFlag(EntityFlag.LAYING_EGG, (boolean) entityMetadata.getValue()); } super.updateBedrockMetadata(entityMetadata, session); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/AbstractHorseEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/AbstractHorseEntity.java index 324c8229f..5f4082e50 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 @@ -64,7 +64,7 @@ public class AbstractHorseEntity extends AnimalEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 16) { + if (entityMetadata.getId() == 17) { byte xd = (byte) entityMetadata.getValue(); metadata.getFlags().setFlag(EntityFlag.TAMED, (xd & 0x02) == 0x02); metadata.getFlags().setFlag(EntityFlag.SADDLED, (xd & 0x04) == 0x04); @@ -106,7 +106,7 @@ public class AbstractHorseEntity extends AnimalEntity { super.updateBedrockMetadata(entityMetadata, session); - if (entityMetadata.getId() == 8) { + if (entityMetadata.getId() == 9) { // Update the health attribute updateBedrockAttributes(session); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/ChestedHorseEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/ChestedHorseEntity.java index 461d636bd..e4f0cc241 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/ChestedHorseEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/ChestedHorseEntity.java @@ -42,7 +42,7 @@ public class ChestedHorseEntity extends AbstractHorseEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 18) { + if (entityMetadata.getId() == 19) { metadata.getFlags().setFlag(EntityFlag.CHESTED, (boolean) entityMetadata.getValue()); } super.updateBedrockMetadata(entityMetadata, session); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/HorseEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/HorseEntity.java index c687898d0..094726de1 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/HorseEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/HorseEntity.java @@ -39,7 +39,7 @@ public class HorseEntity extends AbstractHorseEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 18) { + if (entityMetadata.getId() == 19) { metadata.put(EntityData.VARIANT, entityMetadata.getValue()); metadata.put(EntityData.MARK_VARIANT, (((int) entityMetadata.getValue()) >> 8) % 5); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/LlamaEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/LlamaEntity.java index 8ab1df1a8..67e3304d3 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/LlamaEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/LlamaEntity.java @@ -46,11 +46,11 @@ public class LlamaEntity extends ChestedHorseEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { // Strength - if (entityMetadata.getId() == 19) { + if (entityMetadata.getId() == 20) { metadata.put(EntityData.STRENGTH, entityMetadata.getValue()); } // Color equipped on the llama - if (entityMetadata.getId() == 20) { + if (entityMetadata.getId() == 21) { // Bedrock treats llama decoration as armor MobArmorEquipmentPacket equipmentPacket = new MobArmorEquipmentPacket(); equipmentPacket.setRuntimeEntityId(geyserId); @@ -71,7 +71,7 @@ public class LlamaEntity extends ChestedHorseEntity { session.sendUpstreamPacket(equipmentPacket); } // Color of the llama - if (entityMetadata.getId() == 21) { + if (entityMetadata.getId() == 22) { metadata.put(EntityData.VARIANT, entityMetadata.getValue()); } super.updateBedrockMetadata(entityMetadata, session); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/CatEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/CatEntity.java index 2d56d0c18..a8336636b 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/CatEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/CatEntity.java @@ -49,13 +49,13 @@ public class CatEntity extends TameableEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { super.updateBedrockMetadata(entityMetadata, session); - if (entityMetadata.getId() == 16) { + if (entityMetadata.getId() == 17) { // Update collar color if tamed if (metadata.getFlags().getFlag(EntityFlag.TAMED)) { metadata.put(EntityData.COLOR, collarColor); } } - if (entityMetadata.getId() == 18) { + if (entityMetadata.getId() == 19) { // Different colors in Java and Bedrock for some reason int variantColor; switch ((int) entityMetadata.getValue()) { @@ -76,7 +76,7 @@ public class CatEntity extends TameableEntity { } metadata.put(EntityData.VARIANT, variantColor); } - if (entityMetadata.getId() == 21) { + if (entityMetadata.getId() == 22) { collarColor = (byte) (int) entityMetadata.getValue(); // Needed or else wild cats are a red color if (metadata.getFlags().getFlag(EntityFlag.TAMED)) { diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/ParrotEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/ParrotEntity.java index 45327c785..dcc9d6f78 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/ParrotEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/ParrotEntity.java @@ -41,7 +41,7 @@ public class ParrotEntity extends TameableEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { // Parrot color - if (entityMetadata.getId() == 18) { + if (entityMetadata.getId() == 19) { metadata.put(EntityData.VARIANT, entityMetadata.getValue()); } super.updateBedrockMetadata(entityMetadata, session); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/TameableEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/TameableEntity.java index 21bf0a1b7..923e13712 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/TameableEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/TameableEntity.java @@ -44,7 +44,7 @@ public class TameableEntity extends AnimalEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 16) { + if (entityMetadata.getId() == 17) { byte xd = (byte) entityMetadata.getValue(); metadata.getFlags().setFlag(EntityFlag.SITTING, (xd & 0x01) == 0x01); metadata.getFlags().setFlag(EntityFlag.ANGRY, (xd & 0x02) == 0x02); @@ -52,7 +52,7 @@ public class TameableEntity extends AnimalEntity { } // Note: Must be set for wolf collar color to work - if (entityMetadata.getId() == 17) { + if (entityMetadata.getId() == 18) { if (entityMetadata.getValue() != null) { // Owner UUID of entity Entity entity = session.getEntityCache().getPlayerEntity((UUID) entityMetadata.getValue()); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/WolfEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/WolfEntity.java index bb7916937..0a5d2a58c 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/WolfEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/WolfEntity.java @@ -54,7 +54,7 @@ public class WolfEntity extends TameableEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { //Reset wolf color - if (entityMetadata.getId() == 16) { + if (entityMetadata.getId() == 17) { byte xd = (byte) entityMetadata.getValue(); boolean angry = (xd & 0x02) == 0x02; if (angry) { @@ -63,13 +63,13 @@ public class WolfEntity extends TameableEntity { } // "Begging" on wiki.vg, "Interested" in Nukkit - the tilt of the head - if (entityMetadata.getId() == 18) { + if (entityMetadata.getId() == 19) { metadata.getFlags().setFlag(EntityFlag.INTERESTED, (boolean) entityMetadata.getValue()); } // Wolf collar color // Relies on EntityData.OWNER_EID being set in TameableEntity.java - if (entityMetadata.getId() == 19 && !metadata.getFlags().getFlag(EntityFlag.ANGRY)) { + if (entityMetadata.getId() == 20 && !metadata.getFlags().getFlag(EntityFlag.ANGRY)) { metadata.put(EntityData.COLOR, collarColor = (byte) (int) entityMetadata.getValue()); if (!metadata.containsKey(EntityData.OWNER_EID)) { // If a color is set and there is no owner entity ID, set one. @@ -79,7 +79,7 @@ public class WolfEntity extends TameableEntity { } // Wolf anger (1.16+) - if (entityMetadata.getId() == 20) { + if (entityMetadata.getId() == 21) { metadata.getFlags().setFlag(EntityFlag.ANGRY, (int) entityMetadata.getValue() != 0); metadata.put(EntityData.COLOR, (int) entityMetadata.getValue() != 0 ? (byte) 0 : collarColor); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java index fa5785fe5..f8707398e 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java @@ -85,7 +85,7 @@ public class VillagerEntity extends AbstractMerchantEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 17) { + if (entityMetadata.getId() == 18) { VillagerData villagerData = (VillagerData) entityMetadata.getValue(); // Profession metadata.put(EntityData.VARIANT, VILLAGER_VARIANTS.get(villagerData.getProfession())); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/AbstractSkeletonEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/AbstractSkeletonEntity.java index 2d1b41765..bc17e27ca 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/AbstractSkeletonEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/AbstractSkeletonEntity.java @@ -39,7 +39,7 @@ public class AbstractSkeletonEntity extends MonsterEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 14) { + if (entityMetadata.getId() == 15) { byte xd = (byte) entityMetadata.getValue(); // A bit of a loophole so the hands get raised - set the target ID to its own ID metadata.put(EntityData.TARGET_EID, ((xd & 4) == 4) ? geyserId : 0); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/BasePiglinEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/BasePiglinEntity.java index 0dac92077..57eeb208d 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/BasePiglinEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/BasePiglinEntity.java @@ -40,7 +40,7 @@ public class BasePiglinEntity extends MonsterEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 15) { + if (entityMetadata.getId() == 16) { // Immune to zombification? // Apply shaking effect if not in the nether and zombification is possible metadata.getFlags().setFlag(EntityFlag.SHAKING, !((boolean) entityMetadata.getValue()) && !session.getDimension().equals(DimensionUtils.NETHER)); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/BlazeEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/BlazeEntity.java index dcbb39350..6e1bdce53 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/BlazeEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/BlazeEntity.java @@ -39,7 +39,7 @@ public class BlazeEntity extends MonsterEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 15) { + if (entityMetadata.getId() == 16) { byte xd = (byte) entityMetadata.getValue(); metadata.getFlags().setFlag(EntityFlag.ON_FIRE, (xd & 0x01) == 0x01); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/CreeperEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/CreeperEntity.java index b62337ec2..a1dc02821 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/CreeperEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/CreeperEntity.java @@ -45,15 +45,15 @@ public class CreeperEntity extends MonsterEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 15) { + if (entityMetadata.getId() == 16) { if (!ignitedByFlintAndSteel) { metadata.getFlags().setFlag(EntityFlag.IGNITED, (int) entityMetadata.getValue() == 1); } } - if (entityMetadata.getId() == 16) { + if (entityMetadata.getId() == 17) { metadata.getFlags().setFlag(EntityFlag.POWERED, (boolean) entityMetadata.getValue()); } - if (entityMetadata.getId() == 17) { + if (entityMetadata.getId() == 18) { ignitedByFlintAndSteel = (boolean) entityMetadata.getValue(); metadata.getFlags().setFlag(EntityFlag.IGNITED, ignitedByFlintAndSteel); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonEntity.java index 621679798..74bd5b7ce 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonEntity.java @@ -92,7 +92,7 @@ public class EnderDragonEntity extends InsentientEntity implements Tickable { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 15) { // Phase + if (entityMetadata.getId() == 16) { // Phase phase = (int) entityMetadata.getValue(); phaseTicks = 0; metadata.getFlags().setFlag(EntityFlag.SITTING, isSitting()); @@ -100,7 +100,7 @@ public class EnderDragonEntity extends InsentientEntity implements Tickable { super.updateBedrockMetadata(entityMetadata, session); - if (entityMetadata.getId() == 8) { // Health + if (entityMetadata.getId() == 9) { // Health // Update the health attribute, so that the death animation gets played // Round health up, so that Bedrock doesn't consider the dragon to be dead when health is between 0 and 1 float health = (float) Math.ceil(metadata.getFloat(EntityData.HEALTH)); 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 0d265b56e..f11e57a8f 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,11 +43,11 @@ public class EndermanEntity extends MonsterEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { // Held block - if (entityMetadata.getId() == 15) { + if (entityMetadata.getId() == 16) { metadata.put(EntityData.CARRIED_BLOCK, session.getBlockTranslator().getBedrockBlockId((int) entityMetadata.getValue())); } // "Is screaming" - controls sound - if (entityMetadata.getId() == 16) { + if (entityMetadata.getId() == 17) { if ((boolean) entityMetadata.getValue()) { LevelSoundEvent2Packet packet = new LevelSoundEvent2Packet(); packet.setSound(SoundEvent.STARE); @@ -58,7 +58,7 @@ public class EndermanEntity extends MonsterEntity { } } // "Is staring/provoked" - controls visuals - if (entityMetadata.getId() == 17) { + if (entityMetadata.getId() == 18) { metadata.getFlags().setFlag(EntityFlag.ANGRY, (boolean) entityMetadata.getValue()); } super.updateBedrockMetadata(entityMetadata, session); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/GhastEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/GhastEntity.java index 69bb384a6..22d91ec36 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/GhastEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/GhastEntity.java @@ -40,7 +40,7 @@ public class GhastEntity extends FlyingEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 15) { + if (entityMetadata.getId() == 16) { // If the ghast is attacking metadata.put(EntityData.CHARGE_AMOUNT, (byte) ((boolean) entityMetadata.getValue() ? 1 : 0)); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/GuardianEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/GuardianEntity.java index d254a3299..0b28cd53e 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/GuardianEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/GuardianEntity.java @@ -40,7 +40,7 @@ public class GuardianEntity extends MonsterEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 16) { + if (entityMetadata.getId() == 17) { Entity entity = session.getEntityCache().getEntityByJavaId((int) entityMetadata.getValue()); if (entity == null && session.getPlayerEntity().getEntityId() == (Integer) entityMetadata.getValue()) { entity = session.getPlayerEntity(); 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 44fa2c49d..5b1ccd342 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/PiglinEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/PiglinEntity.java @@ -41,17 +41,17 @@ public class PiglinEntity extends BasePiglinEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 16) { + if (entityMetadata.getId() == 17) { boolean isBaby = (boolean) entityMetadata.getValue(); if (isBaby) { metadata.put(EntityData.SCALE, .55f); metadata.getFlags().setFlag(EntityFlag.BABY, true); } } - if (entityMetadata.getId() == 17) { + if (entityMetadata.getId() == 18) { metadata.getFlags().setFlag(EntityFlag.CHARGING, (boolean) entityMetadata.getValue()); } - if (entityMetadata.getId() == 18) { + if (entityMetadata.getId() == 19) { metadata.getFlags().setFlag(EntityFlag.DANCING, (boolean) entityMetadata.getValue()); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/ShulkerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/ShulkerEntity.java index b99f66ac8..b2e3f834e 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/ShulkerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/ShulkerEntity.java @@ -26,10 +26,8 @@ package org.geysermc.connector.entity.living.monster; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.github.steveice10.mc.protocol.data.game.world.block.BlockFace; import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.connector.entity.living.GolemEntity; @@ -46,16 +44,17 @@ public class ShulkerEntity extends GolemEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 15) { + if (entityMetadata.getId() == 16) { BlockFace blockFace = (BlockFace) entityMetadata.getValue(); metadata.put(EntityData.SHULKER_ATTACH_FACE, (byte) blockFace.ordinal()); } - if (entityMetadata.getId() == 16) { - Position position = (Position) entityMetadata.getValue(); - if (position != null) { - metadata.put(EntityData.SHULKER_ATTACH_POS, Vector3i.from(position.getX(), position.getY(), position.getZ())); - } - } + //TODO - this was removed on Java Edition, but does Bedrock Edition still need it?? +// if (entityMetadata.getId() == 16) { +// Position position = (Position) entityMetadata.getValue(); +// if (position != null) { +// metadata.put(EntityData.SHULKER_ATTACH_POS, Vector3i.from(position.getX(), position.getY(), position.getZ())); +// } +// } if (entityMetadata.getId() == 17) { int height = (byte) entityMetadata.getValue(); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/SpiderEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/SpiderEntity.java index 5706c1d69..65c30289a 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/SpiderEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/SpiderEntity.java @@ -39,7 +39,7 @@ public class SpiderEntity extends MonsterEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 15) { + if (entityMetadata.getId() == 16) { byte xd = (byte) entityMetadata.getValue(); metadata.getFlags().setFlag(EntityFlag.WALL_CLIMBING, (xd & 0x01) == 0x01); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/VexEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/VexEntity.java index cbf0c149a..990a2f3a9 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/VexEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/VexEntity.java @@ -39,7 +39,7 @@ public class VexEntity extends MonsterEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 15) { + if (entityMetadata.getId() == 16) { byte xd = (byte) entityMetadata.getValue(); // Set the target to the player to force the attack animation // even if the player isn't the target as we dont get the target on Java diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/WitherEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/WitherEntity.java index e024b4e55..d6d7f8074 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/WitherEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/WitherEntity.java @@ -44,7 +44,7 @@ public class WitherEntity extends MonsterEntity { public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { long targetID = 0; - if (entityMetadata.getId() >= 15 && entityMetadata.getId() <= 17) { + if (entityMetadata.getId() >= 16 && entityMetadata.getId() <= 18) { Entity entity = session.getEntityCache().getEntityByJavaId((int) entityMetadata.getValue()); if (entity == null && session.getPlayerEntity().getEntityId() == (int) entityMetadata.getValue()) { entity = session.getPlayerEntity(); @@ -55,13 +55,13 @@ public class WitherEntity extends MonsterEntity { } } - if (entityMetadata.getId() == 15) { + if (entityMetadata.getId() == 16) { metadata.put(EntityData.WITHER_TARGET_1, targetID); - } else if (entityMetadata.getId() == 16) { - metadata.put(EntityData.WITHER_TARGET_2, targetID); } else if (entityMetadata.getId() == 17) { - metadata.put(EntityData.WITHER_TARGET_3, targetID); + metadata.put(EntityData.WITHER_TARGET_2, targetID); } else if (entityMetadata.getId() == 18) { + metadata.put(EntityData.WITHER_TARGET_3, targetID); + } else if (entityMetadata.getId() == 19) { metadata.put(EntityData.WITHER_INVULNERABLE_TICKS, entityMetadata.getValue()); // Show the shield for the first few seconds of spawning (like Java) 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 585a1e2ca..dde19927d 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 @@ -40,7 +40,7 @@ public class ZoglinEntity extends MonsterEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 15) { + if (entityMetadata.getId() == 16) { boolean isBaby = (boolean) entityMetadata.getValue(); if (isBaby) { metadata.put(EntityData.SCALE, .55f); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/ZombieEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/ZombieEntity.java index f3e0fdad8..83dbd0332 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/ZombieEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/ZombieEntity.java @@ -40,7 +40,7 @@ public class ZombieEntity extends MonsterEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 15) { + if (entityMetadata.getId() == 16) { boolean isBaby = (boolean) entityMetadata.getValue(); if (isBaby) { metadata.put(EntityData.SCALE, .55f); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/ZombieVillagerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/ZombieVillagerEntity.java index c098fb5f6..d63d2654c 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/ZombieVillagerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/ZombieVillagerEntity.java @@ -42,11 +42,11 @@ public class ZombieVillagerEntity extends ZombieEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 18) { + if (entityMetadata.getId() == 19) { metadata.getFlags().setFlag(EntityFlag.IS_TRANSFORMING, (boolean) entityMetadata.getValue()); metadata.getFlags().setFlag(EntityFlag.SHAKING, (boolean) entityMetadata.getValue()); } - if (entityMetadata.getId() == 19) { + if (entityMetadata.getId() == 20) { VillagerData villagerData = (VillagerData) entityMetadata.getValue(); // Region - only one used on Bedrock metadata.put(EntityData.MARK_VARIANT, VillagerEntity.VILLAGER_REGIONS.get(villagerData.getType())); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/raid/SpellcasterIllagerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/raid/SpellcasterIllagerEntity.java index 6d4500a1c..7e92a7569 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/raid/SpellcasterIllagerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/raid/SpellcasterIllagerEntity.java @@ -45,7 +45,7 @@ public class SpellcasterIllagerEntity extends AbstractIllagerEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 16) { + if (entityMetadata.getId() == 17) { int spellType = (int) (byte) entityMetadata.getValue(); // Summon vex, attack, or wololo metadata.getFlags().setFlag(EntityFlag.CASTING, spellType == 1 || spellType == 2 || spellType == 3); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/raid/VindicatorEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/raid/VindicatorEntity.java index 8705a8030..4a9360393 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/raid/VindicatorEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/raid/VindicatorEntity.java @@ -40,7 +40,7 @@ public class VindicatorEntity extends AbstractIllagerEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { // Allow the axe to be shown if necessary - if (entityMetadata.getId() == 14) { + if (entityMetadata.getId() == 15) { byte xd = (byte) entityMetadata.getValue(); metadata.getFlags().setFlag(EntityFlag.ANGRY, (xd & 4) == 4); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/player/PlayerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/player/PlayerEntity.java index f8eeef307..f057438cb 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/player/PlayerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/player/PlayerEntity.java @@ -273,7 +273,7 @@ public class PlayerEntity extends LivingEntity { } // Extra hearts - is not metadata but an attribute on Bedrock - if (entityMetadata.getId() == 14) { + if (entityMetadata.getId() == 15) { UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket(); attributesPacket.setRuntimeEntityId(geyserId); List attributes = new ArrayList<>(); @@ -283,7 +283,7 @@ public class PlayerEntity extends LivingEntity { session.sendUpstreamPacket(attributesPacket); } - if (entityMetadata.getId() == 16) { + if (entityMetadata.getId() == 17) { // OptionalPack usage for toggling skin bits // In Java Edition, a bit being set means that part should be enabled // However, to ensure that the pack still works on other servers, we invert the bit so all values by default @@ -292,10 +292,10 @@ public class PlayerEntity extends LivingEntity { } // Parrot occupying shoulder - if (entityMetadata.getId() == 18 || entityMetadata.getId() == 19) { + if (entityMetadata.getId() == 19 || entityMetadata.getId() == 20) { CompoundTag tag = (CompoundTag) entityMetadata.getValue(); if (tag != null && !tag.isEmpty()) { - if ((entityMetadata.getId() == 18 && leftParrot != null) || (entityMetadata.getId() == 19 && rightParrot != null)) { + if ((entityMetadata.getId() == 19 && leftParrot != null) || (entityMetadata.getId() == 20 && rightParrot != null)) { // No need to update a parrot's data when it already exists return; } @@ -321,10 +321,10 @@ public class PlayerEntity extends LivingEntity { rightParrot = parrot; } } else { - Entity parrot = (entityMetadata.getId() == 18 ? leftParrot : rightParrot); + Entity parrot = (entityMetadata.getId() == 19 ? leftParrot : rightParrot); if (parrot != null) { parrot.despawnEntity(session); - if (entityMetadata.getId() == 18) { + if (entityMetadata.getId() == 19) { leftParrot = null; } else { rightParrot = null; 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 d182a6f12..15009da21 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,17 +29,18 @@ import com.github.steveice10.mc.protocol.data.game.chunk.Chunk; import com.github.steveice10.mc.protocol.data.game.chunk.Column; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import lombok.Setter; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.utils.MathUtils; public class ChunkCache { - private static final int MINIMUM_WORLD_HEIGHT = 0; - private final boolean cache; - private final Long2ObjectMap chunks; + @Setter + private int minY; + public ChunkCache(GeyserSession session) { if (session.getConnector().getWorldManager().hasOwnChunkCache()) { this.cache = false; // To prevent Spigot from initializing @@ -87,7 +88,7 @@ public class ChunkCache { return; } - if (y < MINIMUM_WORLD_HEIGHT || (y >> 4) > column.getChunks().length - 1) { + if (y < minY || (y >> 4) > column.getChunks().length - 1) { // Y likely goes above or below the height limit of this world return; } @@ -108,7 +109,7 @@ public class ChunkCache { return BlockTranslator.JAVA_AIR_ID; } - if (y < MINIMUM_WORLD_HEIGHT || (y >> 4) > column.getChunks().length - 1) { + if (y < minY || (y >> 4) > column.getChunks().length - 1) { // Y likely goes above or below the height limit of this world return BlockTranslator.JAVA_AIR_ID; } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/TagCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/TagCache.java index c46e37b74..a417a207f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/TagCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/TagCache.java @@ -39,24 +39,51 @@ import java.util.Map; */ public class TagCache { /* Blocks */ - private IntList wool = IntLists.emptyList(); + private IntList leaves; + private IntList wool; + + private IntList axeEffective; + private IntList hoeEffective; + private IntList pickaxeEffective; + private IntList shovelEffective; + /* Items */ - private IntList flowers = IntLists.emptyList(); - private IntList piglinLoved = IntLists.emptyList(); + private IntList flowers; + private IntList foxFood; + private IntList piglinLoved; + + public TagCache() { + // Ensure all lists are non-null + clear(); + } public void loadPacket(ServerDeclareTagsPacket packet) { - Map blockTags = packet.getBlockTags(); + Map blockTags = packet.getTags().get("minecraft:block"); + this.leaves = IntList.of(blockTags.get("minecraft:leaves")); this.wool = IntList.of(blockTags.get("minecraft:wool")); - Map itemTags = packet.getItemTags(); + this.axeEffective = IntList.of(blockTags.get("minecraft:mineable/axe")); + this.hoeEffective = IntList.of(blockTags.get("minecraft:mineable/hoe")); + this.pickaxeEffective = IntList.of(blockTags.get("minecraft:mineable/pickaxe")); + this.shovelEffective = IntList.of(blockTags.get("minecraft:mineable/shovel")); + + Map itemTags = packet.getTags().get("minecraft:item"); this.flowers = IntList.of(itemTags.get("minecraft:flowers")); + this.foxFood = IntList.of(itemTags.get("minecraft:fox_food")); this.piglinLoved = IntList.of(itemTags.get("minecraft:piglin_loved")); } public void clear() { + this.leaves = IntLists.emptyList(); this.wool = IntLists.emptyList(); + this.axeEffective = IntLists.emptyList(); + this.hoeEffective = IntLists.emptyList(); + this.pickaxeEffective = IntLists.emptyList(); + this.shovelEffective = IntLists.emptyList(); + this.flowers = IntLists.emptyList(); + this.foxFood = IntLists.emptyList(); this.piglinLoved = IntLists.emptyList(); } @@ -64,11 +91,32 @@ public class TagCache { return flowers.contains(itemEntry.getJavaId()); } + public boolean isFoxFood(ItemEntry itemEntry) { + return foxFood.contains(itemEntry.getJavaId()); + } + public boolean shouldPiglinAdmire(ItemEntry itemEntry) { return piglinLoved.contains(itemEntry.getJavaId()); } - public boolean isWool(BlockMapping blockMapping) { - return wool.contains(blockMapping.getJavaBlockId()); + public boolean isAxeEffective(BlockMapping blockMapping) { + return axeEffective.contains(blockMapping.getJavaBlockId()); + } + + public boolean isHoeEffective(BlockMapping blockMapping) { + return hoeEffective.contains(blockMapping.getJavaBlockId()); + } + + public boolean isPickaxeEffective(BlockMapping blockMapping) { + return pickaxeEffective.contains(blockMapping.getJavaBlockId()); + } + + public boolean isShovelEffective(BlockMapping blockMapping) { + return shovelEffective.contains(blockMapping.getJavaBlockId()); + } + + public boolean isShearsEffective(BlockMapping blockMapping) { + int javaBlockId = blockMapping.getJavaBlockId(); + return leaves.contains(javaBlockId) || wool.contains(javaBlockId); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaPingPacket.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaPingPacket.java new file mode 100644 index 000000000..c324a81c4 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaPingPacket.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.java; + +import com.github.steveice10.mc.protocol.packet.ingame.client.ClientPongPacket; +import com.github.steveice10.mc.protocol.packet.ingame.server.ServerPingPacket; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; + +// Why does this packet exist? Whatever, we better implement it +@Translator(packet = ServerPingPacket.class) +public class JavaPingPacket extends PacketTranslator { + + @Override + public void translate(ServerPingPacket packet, GeyserSession session) { + session.sendDownstreamPacket(new ClientPongPacket(packet.getId())); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/GeyserWorldManager.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/GeyserWorldManager.java index 014f3e366..80bb08b06 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/GeyserWorldManager.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/GeyserWorldManager.java @@ -25,8 +25,6 @@ package org.geysermc.connector.network.translators.world; -import com.github.steveice10.mc.protocol.data.game.chunk.Chunk; -import com.github.steveice10.mc.protocol.data.game.chunk.Column; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import com.github.steveice10.mc.protocol.data.game.setting.Difficulty; import com.github.steveice10.mc.protocol.packet.ingame.client.ClientChatPacket; @@ -53,45 +51,12 @@ public class GeyserWorldManager extends WorldManager { return BlockTranslator.JAVA_AIR_ID; } - @Override - public void getBlocksInSection(GeyserSession session, int x, int y, int z, Chunk chunk) { - ChunkCache chunkCache = session.getChunkCache(); - Column cachedColumn; - Chunk cachedChunk; - if (chunkCache == null || (cachedColumn = chunkCache.getChunk(x, z)) == null || (cachedChunk = cachedColumn.getChunks()[y]) == null) { - return; - } - - // Copy state IDs from cached chunk to output chunk - for (int blockY = 0; blockY < 16; blockY++) { // Cache-friendly iteration order - for (int blockZ = 0; blockZ < 16; blockZ++) { - for (int blockX = 0; blockX < 16; blockX++) { - chunk.set(blockX, blockY, blockZ, cachedChunk.get(blockX, blockY, blockZ)); - } - } - } - } - @Override public boolean hasOwnChunkCache() { // This implementation can only fetch data from the session chunk cache return false; } - @Override - public int[] getBiomeDataAt(GeyserSession session, int x, int z) { - if (session.getConnector().getConfig().isCacheChunks()) { - ChunkCache chunkCache = session.getChunkCache(); - if (chunkCache != null) { // Chunk cache can be null if the session is closed asynchronously - Column column = chunkCache.getChunk(x, z); - if (column != null) { // Column can be null if the server sent a partial chunk update before the first ground-up-continuous one - return column.getBiomeData(); - } - } - } - return new int[1024]; - } - @Override public NbtMap getLecternDataAt(GeyserSession session, int x, int y, int z, boolean isChunkLoad) { // Without direct server access, we can't get lectern information on-the-fly. diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/WorldManager.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/WorldManager.java index e97dcec32..ca945bfb4 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/WorldManager.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/WorldManager.java @@ -25,7 +25,6 @@ package org.geysermc.connector.network.translators.world; -import com.github.steveice10.mc.protocol.data.game.chunk.Chunk; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import com.github.steveice10.mc.protocol.data.game.setting.Difficulty; @@ -76,17 +75,6 @@ public abstract class WorldManager { */ public abstract int getBlockAt(GeyserSession session, int x, int y, int z); - /** - * Gets all block states in the specified chunk section. - * - * @param session the session - * @param x the chunk's X coordinate - * @param y the chunk's Y coordinate - * @param z the chunk's Z coordinate - * @param section the chunk section to store the block data in - */ - public abstract void getBlocksInSection(GeyserSession session, int x, int y, int z, Chunk section); - /** * Checks whether or not this world manager requires a separate chunk cache/has access to more block data than the chunk cache. *

@@ -97,16 +85,6 @@ public abstract class WorldManager { */ public abstract boolean hasOwnChunkCache(); - /** - * Gets the Java biome data for the specified chunk. - * - * @param session the session of the player - * @param x the chunk's X coordinate - * @param z the chunk's Z coordinate - * @return the biome data for the specified region with a length of 1024. - */ - public abstract int[] getBiomeDataAt(GeyserSession session, int x, int z); - /** * Sigh.
* 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 3693d34cc..1737415f7 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 @@ -143,12 +143,7 @@ public abstract class BlockTranslator { builder.canBreakWithHand(false); } - JsonNode toolTypeNode = entry.getValue().get("tool_type"); - if (toolTypeNode != null) { - builder.toolType(toolTypeNode.textValue()); - } else { - builder.toolType(""); - } + builder.toolType(""); //TODO JsonNode collisionIndexNode = entry.getValue().get("collision_index"); if (hardnessNode != null) { @@ -225,7 +220,7 @@ public abstract class BlockTranslator { throw new AssertionError("Unable to find Java water in palette"); } JAVA_WATER_ID = waterRuntimeId; - + BlockMapping.AIR = JAVA_RUNTIME_ID_TO_BLOCK_MAPPING.get(JAVA_AIR_ID); BlockTranslator1_16_210.init(); diff --git a/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java b/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java index 01c85ab43..109aa1564 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java @@ -41,16 +41,28 @@ public class BlockUtils { */ public static final Position POSITION_ZERO = new Position(0, 0, 0); - private static boolean correctTool(String blockToolType, String itemToolType) { - return (blockToolType.equals("sword") && itemToolType.equals("sword")) || - (blockToolType.equals("shovel") && itemToolType.equals("shovel")) || - (blockToolType.equals("pickaxe") && itemToolType.equals("pickaxe")) || - (blockToolType.equals("axe") && itemToolType.equals("axe")) || - (blockToolType.equals("shears") && itemToolType.equals("shears")); + private static boolean correctTool(GeyserSession session, BlockMapping blockMapping, String itemToolType) { + switch (itemToolType) { + case "axe": + return session.getTagCache().isAxeEffective(blockMapping); + case "hoe": + return session.getTagCache().isHoeEffective(blockMapping); + case "pickaxe": + return session.getTagCache().isPickaxeEffective(blockMapping); + case "shears": + return session.getTagCache().isShearsEffective(blockMapping); + case "shovel": + return session.getTagCache().isShovelEffective(blockMapping); + case "sword": + return blockMapping.getJavaBlockId() == BlockTranslator.JAVA_COBWEB_BLOCK_ID; + default: + session.getConnector().getLogger().warning("Unknown tool type: " + itemToolType); + return false; + } } - private static double toolBreakTimeBonus(String toolType, String toolTier, boolean isWoolBlock) { - if (toolType.equals("shears")) return isWoolBlock ? 5.0 : 15.0; + private static double toolBreakTimeBonus(String toolType, String toolTier, boolean isShearsEffective) { + if (toolType.equals("shears")) return isShearsEffective ? 5.0 : 15.0; if (toolType.equals("")) return 1.0; switch (toolTier) { // https://minecraft.gamepedia.com/Breaking#Speed @@ -73,16 +85,14 @@ public class BlockUtils { //http://minecraft.gamepedia.com/Breaking private static double calculateBreakTime(double blockHardness, String toolTier, boolean canHarvestWithHand, boolean correctTool, - String toolType, boolean isWoolBlock, boolean isCobweb, int toolEfficiencyLevel, int hasteLevel, int miningFatigueLevel, + String toolType, boolean isShearsEffective, int toolEfficiencyLevel, int hasteLevel, int miningFatigueLevel, boolean insideOfWaterWithoutAquaAffinity, boolean outOfWaterButNotOnGround, boolean insideWaterAndNotOnGround) { double baseTime = ((correctTool || canHarvestWithHand) ? 1.5 : 5.0) * blockHardness; double speed = 1.0 / baseTime; if (correctTool) { - speed *= toolBreakTimeBonus(toolType, toolTier, isWoolBlock); + speed *= toolBreakTimeBonus(toolType, toolTier, isShearsEffective); speed += toolEfficiencyLevel == 0 ? 0 : toolEfficiencyLevel * toolEfficiencyLevel + 1; - } else if (toolType.equals("sword")) { - speed*= (isCobweb ? 15.0 : 1.5); } speed *= 1.0 + (0.2 * hasteLevel); @@ -110,9 +120,7 @@ public class BlockUtils { } public static double getBreakTime(GeyserSession session, BlockMapping blockMapping, ItemEntry item, CompoundTag nbtData, boolean isSessionPlayer) { - boolean isWoolBlock = session.getTagCache().isWool(blockMapping); - boolean isCobweb = blockMapping.getJavaBlockId() == BlockTranslator.JAVA_COBWEB_BLOCK_ID; - String blockToolType = blockMapping.getToolType(); + boolean isShearsEffective = session.getTagCache().isShearsEffective(blockMapping); //TODO called twice boolean canHarvestWithHand = blockMapping.isCanBreakWithHand(); String toolType = ""; String toolTier = ""; @@ -121,7 +129,7 @@ public class BlockUtils { ToolItemEntry toolItem = (ToolItemEntry) item; toolType = toolItem.getToolType(); toolTier = toolItem.getToolTier(); - correctTool = correctTool(blockToolType, toolType); + correctTool = correctTool(session, blockMapping, toolType); } int toolEfficiencyLevel = ItemUtils.getEnchantmentLevel(nbtData, "minecraft:efficiency"); int hasteLevel = 0; @@ -129,8 +137,8 @@ public class BlockUtils { if (!isSessionPlayer) { // Another entity is currently mining; we have all the information we know - return calculateBreakTime(blockMapping.getHardness(), toolTier, canHarvestWithHand, correctTool, toolType, isWoolBlock, - isCobweb, toolEfficiencyLevel, hasteLevel, miningFatigueLevel, false, + return calculateBreakTime(blockMapping.getHardness(), toolTier, canHarvestWithHand, correctTool, toolType, isShearsEffective, + toolEfficiencyLevel, hasteLevel, miningFatigueLevel, false, false, false); } @@ -144,8 +152,8 @@ public class BlockUtils { boolean outOfWaterButNotOnGround = (!isInWater) && (!session.getPlayerEntity().isOnGround()); boolean insideWaterNotOnGround = isInWater && !session.getPlayerEntity().isOnGround(); - return calculateBreakTime(blockMapping.getHardness(), toolTier, canHarvestWithHand, correctTool, toolType, isWoolBlock, - isCobweb, toolEfficiencyLevel, hasteLevel, miningFatigueLevel, insideOfWaterWithoutAquaAffinity, + return calculateBreakTime(blockMapping.getHardness(), toolTier, canHarvestWithHand, correctTool, toolType, isShearsEffective, + toolEfficiencyLevel, hasteLevel, miningFatigueLevel, insideOfWaterWithoutAquaAffinity, outOfWaterButNotOnGround, insideWaterNotOnGround); } 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 7838adde2..b7370d1ff 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java @@ -82,7 +82,17 @@ public class ChunkUtils { public static ChunkData translateToBedrock(GeyserSession session, Column column) { Chunk[] javaSections = column.getChunks(); - ChunkSection[] sections = new ChunkSection[javaSections.length]; + + //FIXME TEMPORARY UNTIL THE CAVES AND CLIFFS EXPERIMENTAL DATA IS REMOVED UNLESS IT'S NOT REMOVED THEN HMMMM + int sectionYOffset; + if (session.getDimension().equals(DimensionUtils.OVERWORLD)) { + sectionYOffset = 4; + } else { + sectionYOffset = 0; + } + //FIXME END + + ChunkSection[] sections = new ChunkSection[javaSections.length + sectionYOffset]; // Temporarily stores compound tags of Bedrock-only block entities List bedrockOnlyBlockEntities = new ArrayList<>(); @@ -195,7 +205,7 @@ public class ChunkUtils { layers = new BlockStorage[]{ layer0, new BlockStorage(BitArrayVersion.V1.createArray(BlockStorage.SIZE, layer1Data), layer1Palette) }; } - sections[sectionY] = new ChunkSection(layers); + sections[sectionY + sectionYOffset] = new ChunkSection(layers); } CompoundTag[] blockEntities = column.getTileEntities(); From c5881276487c8ad8b950b953dd8e984e0a356e8a Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 19 May 2021 22:34:50 -0400 Subject: [PATCH 046/107] Yeet block tool type This is 99% controlled by the server now. --- .../network/translators/world/block/BlockTranslator.java | 2 -- .../java/org/geysermc/connector/registry/type/BlockMapping.java | 2 -- 2 files changed, 4 deletions(-) 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 1737415f7..efa3a7c2b 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 @@ -143,8 +143,6 @@ public abstract class BlockTranslator { builder.canBreakWithHand(false); } - builder.toolType(""); //TODO - JsonNode collisionIndexNode = entry.getValue().get("collision_index"); if (hardnessNode != null) { builder.collisionIndex(collisionIndexNode.intValue()); diff --git a/connector/src/main/java/org/geysermc/connector/registry/type/BlockMapping.java b/connector/src/main/java/org/geysermc/connector/registry/type/BlockMapping.java index f3eaddd91..99045bb22 100644 --- a/connector/src/main/java/org/geysermc/connector/registry/type/BlockMapping.java +++ b/connector/src/main/java/org/geysermc/connector/registry/type/BlockMapping.java @@ -28,7 +28,6 @@ package org.geysermc.connector.registry.type; import lombok.Builder; import lombok.Value; -import javax.annotation.Nonnull; import javax.annotation.Nullable; @Builder @@ -45,7 +44,6 @@ public class BlockMapping { double hardness; boolean canBreakWithHand; - @Nonnull String toolType; /** * The index of this collision in collision.json */ From 1f83a5ac9fc398db2679cafa22a53f9bb8c81de1 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 23 May 2021 22:32:42 -0400 Subject: [PATCH 047/107] Respect tool tier requirement for block breaking (#1837) --- connector/pom.xml | 1 + .../network/session/cache/TagCache.java | 24 ++++++++++++ .../geysermc/connector/utils/BlockUtils.java | 39 ++++++++++++++++--- 3 files changed, 58 insertions(+), 6 deletions(-) diff --git a/connector/pom.xml b/connector/pom.xml index 6529dbcd8..9ada540ef 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -305,6 +305,7 @@ String GIT_VERSION = ".*" + String GIT_VERSION = "git-${git.branch}-${git.commit.id.abbrev}" diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/TagCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/TagCache.java index a417a207f..b1427a864 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/TagCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/TagCache.java @@ -47,6 +47,10 @@ public class TagCache { private IntList pickaxeEffective; private IntList shovelEffective; + private IntList requiresStoneTool; + private IntList requiresIronTool; + private IntList requiresDiamondTool; + /* Items */ private IntList flowers; private IntList foxFood; @@ -67,6 +71,10 @@ public class TagCache { this.pickaxeEffective = IntList.of(blockTags.get("minecraft:mineable/pickaxe")); this.shovelEffective = IntList.of(blockTags.get("minecraft:mineable/shovel")); + this.requiresStoneTool = IntList.of(blockTags.get("minecraft:needs_stone_tool")); + this.requiresIronTool = IntList.of(blockTags.get("minecraft:needs_iron_tool")); + this.requiresDiamondTool = IntList.of(blockTags.get("minecraft:needs_diamond_tool")); + Map itemTags = packet.getTags().get("minecraft:item"); this.flowers = IntList.of(itemTags.get("minecraft:flowers")); this.foxFood = IntList.of(itemTags.get("minecraft:fox_food")); @@ -82,6 +90,10 @@ public class TagCache { this.pickaxeEffective = IntLists.emptyList(); this.shovelEffective = IntLists.emptyList(); + this.requiresStoneTool = IntLists.emptyList(); + this.requiresIronTool = IntLists.emptyList(); + this.requiresDiamondTool = IntLists.emptyList(); + this.flowers = IntLists.emptyList(); this.foxFood = IntLists.emptyList(); this.piglinLoved = IntLists.emptyList(); @@ -119,4 +131,16 @@ public class TagCache { int javaBlockId = blockMapping.getJavaBlockId(); return leaves.contains(javaBlockId) || wool.contains(javaBlockId); } + + public boolean requiresStoneTool(BlockMapping blockMapping) { + return requiresStoneTool.contains(blockMapping.getJavaBlockId()); + } + + public boolean requiresIronTool(BlockMapping blockMapping) { + return requiresIronTool.contains(blockMapping.getJavaBlockId()); + } + + public boolean requiresDiamondTool(BlockMapping blockMapping) { + return requiresDiamondTool.contains(blockMapping.getJavaBlockId()); + } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java b/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java index 109aa1564..4dc87d5d8 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java @@ -83,11 +83,36 @@ public class BlockUtils { } } - //http://minecraft.gamepedia.com/Breaking - private static double calculateBreakTime(double blockHardness, String toolTier, boolean canHarvestWithHand, boolean correctTool, + private static boolean canToolTierBreakBlock(GeyserSession session, BlockMapping blockMapping, String toolTier) { + if (toolTier.equals("netherite") || toolTier.equals("diamond")) { + // As of 1.17, these tiers can mine everything that is mineable + return true; + } + + switch (toolTier) { + // Use intentional fall-throughs to check each tier with this block + default: + if (session.getTagCache().requiresStoneTool(blockMapping)) { + return false; + } + case "stone": + if (session.getTagCache().requiresIronTool(blockMapping)) { + return false; + } + case "iron": + if (session.getTagCache().requiresDiamondTool(blockMapping)) { + return false; + } + } + + return true; + } + + // https://minecraft.gamepedia.com/Breaking + private static double calculateBreakTime(double blockHardness, String toolTier, boolean canHarvestWithHand, boolean correctTool, boolean canTierMineBlock, String toolType, boolean isShearsEffective, int toolEfficiencyLevel, int hasteLevel, int miningFatigueLevel, boolean insideOfWaterWithoutAquaAffinity, boolean outOfWaterButNotOnGround, boolean insideWaterAndNotOnGround) { - double baseTime = ((correctTool || canHarvestWithHand) ? 1.5 : 5.0) * blockHardness; + double baseTime = (((correctTool && canTierMineBlock) || canHarvestWithHand) ? 1.5 : 5.0) * blockHardness; double speed = 1.0 / baseTime; if (correctTool) { @@ -125,11 +150,13 @@ public class BlockUtils { String toolType = ""; String toolTier = ""; boolean correctTool = false; + boolean toolCanBreak = false; if (item instanceof ToolItemEntry) { ToolItemEntry toolItem = (ToolItemEntry) item; toolType = toolItem.getToolType(); toolTier = toolItem.getToolTier(); correctTool = correctTool(session, blockMapping, toolType); + toolCanBreak = canToolTierBreakBlock(session, blockMapping, toolTier); } int toolEfficiencyLevel = ItemUtils.getEnchantmentLevel(nbtData, "minecraft:efficiency"); int hasteLevel = 0; @@ -137,12 +164,12 @@ public class BlockUtils { if (!isSessionPlayer) { // Another entity is currently mining; we have all the information we know - return calculateBreakTime(blockMapping.getHardness(), toolTier, canHarvestWithHand, correctTool, toolType, isShearsEffective, + return calculateBreakTime(blockMapping.getHardness(), toolTier, canHarvestWithHand, correctTool, toolCanBreak, toolType, isShearsEffective, toolEfficiencyLevel, hasteLevel, miningFatigueLevel, false, false, false); } - hasteLevel = session.getEffectCache().getEffectLevel(Effect.FASTER_DIG); + hasteLevel = Math.max(session.getEffectCache().getEffectLevel(Effect.FASTER_DIG), session.getEffectCache().getEffectLevel(Effect.CONDUIT_POWER)); miningFatigueLevel = session.getEffectCache().getEffectLevel(Effect.SLOWER_DIG); boolean isInWater = session.getCollisionManager().isPlayerInWater(); @@ -152,7 +179,7 @@ public class BlockUtils { boolean outOfWaterButNotOnGround = (!isInWater) && (!session.getPlayerEntity().isOnGround()); boolean insideWaterNotOnGround = isInWater && !session.getPlayerEntity().isOnGround(); - return calculateBreakTime(blockMapping.getHardness(), toolTier, canHarvestWithHand, correctTool, toolType, isShearsEffective, + return calculateBreakTime(blockMapping.getHardness(), toolTier, canHarvestWithHand, correctTool, toolCanBreak, toolType, isShearsEffective, toolEfficiencyLevel, hasteLevel, miningFatigueLevel, insideOfWaterWithoutAquaAffinity, outOfWaterButNotOnGround, insideWaterNotOnGround); } From 505de0e0d5cb7d0b213dc41e5c9e310334d91f5c Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 24 May 2021 19:25:35 -0400 Subject: [PATCH 048/107] Remove Geyser-Bukkit migration --- .../org/geysermc/platform/spigot/GeyserSpigotPlugin.java | 7 ------- 1 file changed, 7 deletions(-) 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 d9b3c3a93..8d8755e73 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 @@ -55,7 +55,6 @@ import us.myles.ViaVersion.api.protocol.ProtocolVersion; import java.io.File; import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.UUID; @@ -81,12 +80,6 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { try { if (!getDataFolder().exists()) { getDataFolder().mkdir(); - File bukkitConfig = new File("plugins/Geyser-Bukkit/config.yml"); - if (bukkitConfig.exists()) { // Copy over old configs - 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, 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); From cfa2805e0031af3d200e3fce5a65cd9936cb12cd Mon Sep 17 00:00:00 2001 From: Tim203 Date: Wed, 26 May 2021 01:55:58 +0200 Subject: [PATCH 049/107] Make sure that the time we use is always the same across servers --- .../floodgate/time/SntpClientUtils.java | 91 +++++++++++++++++++ .../geysermc/floodgate/time/TimeSyncer.java | 59 ++++++++++++ .../geysermc/floodgate/util/BedrockData.java | 27 ++++-- .../geysermc/connector/GeyserConnector.java | 9 +- .../network/session/GeyserSession.java | 3 +- .../geysermc/connector/utils/Constants.java | 1 + 6 files changed, 179 insertions(+), 11 deletions(-) create mode 100644 common/src/main/java/org/geysermc/floodgate/time/SntpClientUtils.java create mode 100644 common/src/main/java/org/geysermc/floodgate/time/TimeSyncer.java diff --git a/common/src/main/java/org/geysermc/floodgate/time/SntpClientUtils.java b/common/src/main/java/org/geysermc/floodgate/time/SntpClientUtils.java new file mode 100644 index 000000000..9e7f2c1b0 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/time/SntpClientUtils.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.floodgate.time; + +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.nio.ByteBuffer; + +/* + * Thanks: + * https://datatracker.ietf.org/doc/html/rfc1769 + * https://github.com/jonsagara/SimpleNtpClient + * https://stackoverflow.com/a/29138806 + */ +public final class SntpClientUtils { + private static final int NTP_PORT = 123; + + private static final int NTP_PACKET_SIZE = 48; + private static final int NTP_MODE = 3; // client + private static final int NTP_VERSION = 3; + private static final int RECEIVE_TIME_POSITION = 32; + + private static final long NTP_TIME_OFFSET = ((365L * 70L) + 17L) * 24L * 60L * 60L; + + public static long requestTimeOffset(String host, int timeout) { + try (DatagramSocket socket = new DatagramSocket()) { + socket.setSoTimeout(timeout); + + InetAddress address = InetAddress.getByName(host); + + ByteBuffer buff = ByteBuffer.allocate(NTP_PACKET_SIZE); + + DatagramPacket request = new DatagramPacket( + buff.array(), NTP_PACKET_SIZE, address, NTP_PORT + ); + + // mode is in the least signification 3 bits + // version is in bits 3-5 + buff.put((byte) (NTP_MODE | (NTP_VERSION << 3))); + + long originateTime = System.currentTimeMillis(); + socket.send(request); + + DatagramPacket response = new DatagramPacket(buff.array(), NTP_PACKET_SIZE); + socket.receive(response); + + long responseTime = System.currentTimeMillis(); + + // everything before isn't important for us + buff.position(RECEIVE_TIME_POSITION); + + long receiveTime = readTimestamp(buff); + long transmitTime = readTimestamp(buff); + + return ((receiveTime - originateTime) + (transmitTime - responseTime)) / 2; + } catch (Exception ignored) { + } + return Long.MIN_VALUE; + } + + private static long readTimestamp(ByteBuffer buffer) { + //todo look into the ntp 2036 problem + long seconds = buffer.getInt() & 0xffffffffL; + long fraction = buffer.getInt() & 0xffffffffL; + return ((seconds - NTP_TIME_OFFSET) * 1000) + ((fraction * 1000) / 0x100000000L); + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/time/TimeSyncer.java b/common/src/main/java/org/geysermc/floodgate/time/TimeSyncer.java new file mode 100644 index 000000000..2d74e4b77 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/time/TimeSyncer.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.floodgate.time; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +public final class TimeSyncer { + private final ExecutorService executorService; + private long timeOffset = Long.MIN_VALUE; // value when it failed to get the offset + + public TimeSyncer(String timeServer) { + ScheduledExecutorService service = Executors.newScheduledThreadPool(1); + service.scheduleWithFixedDelay(() -> { + // 5 tries to get the time offset + for (int i = 0; i < 5; i++) { + long offset = SntpClientUtils.requestTimeOffset(timeServer, 3000); + if (offset != Long.MIN_VALUE) { + timeOffset = offset; + return; + } + } + }, 0, 30, TimeUnit.MINUTES); + executorService = service; + } + + public void shutdown() { + executorService.shutdown(); + } + + public long getTimeOffset() { + return timeOffset; + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java index 81a6307a2..46c1d3d1a 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java +++ b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java @@ -28,6 +28,7 @@ package org.geysermc.floodgate.util; import lombok.AccessLevel; import lombok.Getter; import lombok.RequiredArgsConstructor; +import org.geysermc.floodgate.time.TimeSyncer; /** * This class contains the raw data send by Geyser to Floodgate or from Floodgate to Floodgate. This @@ -56,20 +57,28 @@ public final class BedrockData implements Cloneable { private final long timestamp; private final int dataLength; - public static BedrockData of(String version, String username, String xuid, int deviceOs, - String languageCode, int uiProfile, int inputMode, String ip, - LinkedPlayer linkedPlayer, boolean fromProxy, int subscribeId, - String verifyCode) { + public static BedrockData of( + String version, String username, String xuid, int deviceOs, + String languageCode, int uiProfile, int inputMode, String ip, + LinkedPlayer linkedPlayer, boolean fromProxy, int subscribeId, + String verifyCode, TimeSyncer timeSyncer) { + + long realMillis = System.currentTimeMillis(); + if (timeSyncer.getTimeOffset() != Long.MIN_VALUE) { + realMillis += timeSyncer.getTimeOffset(); + } + return new BedrockData(version, username, xuid, deviceOs, languageCode, inputMode, uiProfile, ip, linkedPlayer, fromProxy, subscribeId, verifyCode, - System.currentTimeMillis(), EXPECTED_LENGTH); + realMillis, EXPECTED_LENGTH); } - public static BedrockData of(String version, String username, String xuid, int deviceOs, - String languageCode, int uiProfile, int inputMode, String ip, - int subscribeId, String verifyCode) { + public static BedrockData of( + String version, String username, String xuid, int deviceOs, + String languageCode, int uiProfile, int inputMode, String ip, + int subscribeId, String verifyCode, TimeSyncer timeSyncer) { return of(version, username, xuid, deviceOs, languageCode, uiProfile, inputMode, ip, null, - false, subscribeId, verifyCode); + false, subscribeId, verifyCode, timeSyncer); } public static BedrockData fromString(String data) { diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index bac915437..d1122b65f 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -62,6 +62,7 @@ import org.geysermc.floodgate.crypto.AesCipher; import org.geysermc.floodgate.crypto.AesKeyProducer; import org.geysermc.floodgate.crypto.Base64Topping; import org.geysermc.floodgate.crypto.FloodgateCipher; +import org.geysermc.floodgate.time.TimeSyncer; import org.jetbrains.annotations.Contract; import javax.naming.directory.Attribute; @@ -79,7 +80,6 @@ import java.util.concurrent.TimeUnit; @Getter public class GeyserConnector { - public static final ObjectMapper JSON_MAPPER = new ObjectMapper() .enable(JsonParser.Feature.IGNORE_UNDEFINED) .enable(JsonParser.Feature.ALLOW_COMMENTS) @@ -106,6 +106,7 @@ public class GeyserConnector { @Setter private AuthType defaultAuthType; + private TimeSyncer timeSyncer; private FloodgateCipher cipher; private FloodgateSkinUploader skinUploader; @@ -198,6 +199,7 @@ public class GeyserConnector { defaultAuthType = AuthType.getByName(config.getRemote().getAuthType()); if (defaultAuthType == AuthType.FLOODGATE) { + timeSyncer = new TimeSyncer(Constants.NTP_SERVER); try { Key key = new AesKeyProducer().produceFrom(config.getFloodgateKeyPath()); cipher = new AesCipher(new Base64Topping()); @@ -353,6 +355,7 @@ public class GeyserConnector { generalThreadPool.shutdown(); bedrockServer.close(); + timeSyncer.shutdown(); players.clear(); defaultAuthType = null; this.getCommandManager().getCommands().clear(); @@ -431,6 +434,10 @@ public class GeyserConnector { return bootstrap.getWorldManager(); } + public TimeSyncer getTimeSyncer() { + return timeSyncer; + } + /** * Whether to use XML reflections in the jar or manually find the reflections. * Will return true if the version number is not 'DEV' and the platform is not Fabric. 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 b922903b6..15d246fee 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 @@ -691,7 +691,8 @@ public class GeyserSession implements CommandSender { clientData.getCurrentInputMode().ordinal(), upstream.getAddress().getAddress().getHostAddress(), skinUploader.getId(), - skinUploader.getVerifyCode() + skinUploader.getVerifyCode(), + connector.getTimeSyncer() ).toString()); } catch (Exception e) { connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.encrypt_fail"), e); diff --git a/connector/src/main/java/org/geysermc/connector/utils/Constants.java b/connector/src/main/java/org/geysermc/connector/utils/Constants.java index 8dcd63d3e..5b21d045c 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/Constants.java +++ b/connector/src/main/java/org/geysermc/connector/utils/Constants.java @@ -30,6 +30,7 @@ import java.net.URISyntaxException; public final class Constants { public static final URI SKIN_UPLOAD_URI; + public static final String NTP_SERVER = "time.cloudflare.com"; static { URI skinUploadUri = null; From 934fc12b161f10c4a4e325b69d0ef3ba0a65b29a Mon Sep 17 00:00:00 2001 From: Tim203 Date: Wed, 26 May 2021 20:20:59 +0200 Subject: [PATCH 050/107] Warn if we were unable to check if the system clock is accurate --- .../org/geysermc/floodgate/time/TimeSyncer.java | 13 ++++++++++++- .../org/geysermc/floodgate/util/BedrockData.java | 8 +------- .../connector/network/session/GeyserSession.java | 8 ++++++++ 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/common/src/main/java/org/geysermc/floodgate/time/TimeSyncer.java b/common/src/main/java/org/geysermc/floodgate/time/TimeSyncer.java index 2d74e4b77..1e385b2c6 100644 --- a/common/src/main/java/org/geysermc/floodgate/time/TimeSyncer.java +++ b/common/src/main/java/org/geysermc/floodgate/time/TimeSyncer.java @@ -37,7 +37,7 @@ public final class TimeSyncer { public TimeSyncer(String timeServer) { ScheduledExecutorService service = Executors.newScheduledThreadPool(1); service.scheduleWithFixedDelay(() -> { - // 5 tries to get the time offset + // 5 tries to get the time offset, since UDP doesn't guaranty a response for (int i = 0; i < 5; i++) { long offset = SntpClientUtils.requestTimeOffset(timeServer, 3000); if (offset != Long.MIN_VALUE) { @@ -56,4 +56,15 @@ public final class TimeSyncer { public long getTimeOffset() { return timeOffset; } + + public long getRealMillis() { + if (hasUsefulOffset()) { + return System.currentTimeMillis() + getTimeOffset(); + } + return System.currentTimeMillis(); + } + + public boolean hasUsefulOffset() { + return timeOffset != Long.MIN_VALUE; + } } diff --git a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java index 46c1d3d1a..4f4325a9b 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java +++ b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java @@ -62,15 +62,9 @@ public final class BedrockData implements Cloneable { String languageCode, int uiProfile, int inputMode, String ip, LinkedPlayer linkedPlayer, boolean fromProxy, int subscribeId, String verifyCode, TimeSyncer timeSyncer) { - - long realMillis = System.currentTimeMillis(); - if (timeSyncer.getTimeOffset() != Long.MIN_VALUE) { - realMillis += timeSyncer.getTimeOffset(); - } - return new BedrockData(version, username, xuid, deviceOs, languageCode, inputMode, uiProfile, ip, linkedPlayer, fromProxy, subscribeId, verifyCode, - realMillis, EXPECTED_LENGTH); + timeSyncer.getRealMillis(), EXPECTED_LENGTH); } public static BedrockData of( 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 9068323e6..eddc4c5a1 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 @@ -695,6 +695,14 @@ public class GeyserSession implements CommandSender { skinUploader.getVerifyCode(), connector.getTimeSyncer() ).toString()); + + if (!connector.getTimeSyncer().hasUsefulOffset()) { + connector.getLogger().warning( + "We couldn't make sure that your system clock is accurate. " + + "This can cause issues with logging in." + ); + } + } catch (Exception e) { connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.encrypt_fail"), e); disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.floodgate.encryption_fail", getClientData().getLanguageCode())); From 3c695700f39f968bcc053654dbea5e70530f8695 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 28 May 2021 19:19:44 -0400 Subject: [PATCH 051/107] Update to 1.17-pre1 --- connector/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/pom.xml b/connector/pom.xml index 9ada540ef..ef14a4704 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -122,7 +122,7 @@ com.github.steveice10 mcprotocollib - 21w20a-SNAPSHOT + 1.17-pre1-SNAPSHOT compile From 360e2f4b9a6efd35d7876e32e99ab6311ac6f342 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sun, 30 May 2021 00:22:11 +0200 Subject: [PATCH 052/107] Always replace spaces in usernames when using Floodgate Bungeecord recently started checking usernames for spaces in the login start packet. To resolve this we just always send the username without spaces in the login start packet. Floodgate is still able to get the real username of the Bedrock player and Floodgate is also still in charge of the final username. --- .../connector/network/session/GeyserSession.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index eddc4c5a1..68e7f5510 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 @@ -581,7 +581,14 @@ public class GeyserSession implements CommandSender { protocol = new MinecraftProtocol(authenticationService.getSelectedProfile(), authenticationService.getAccessToken()); } else { - protocol = new MinecraftProtocol(username); + // always replace spaces when using Floodgate, + // as usernames with spaces cause issues with Bungeecord's login cycle + String validUsername = username; + if (remoteAuthType == AuthType.FLOODGATE) { + validUsername = username.replace(' ', '_'); + } + + protocol = new MinecraftProtocol(validUsername); } connectDownstream(); From db583135eb2e3760a85c81d7bae9fab764ec3b99 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sun, 30 May 2021 00:51:04 +0200 Subject: [PATCH 053/107] Improved the comment added in the previous commit --- .../org/geysermc/connector/network/session/GeyserSession.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 68e7f5510..b30b181b6 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 @@ -582,7 +582,9 @@ public class GeyserSession implements CommandSender { protocol = new MinecraftProtocol(authenticationService.getSelectedProfile(), authenticationService.getAccessToken()); } else { // always replace spaces when using Floodgate, - // as usernames with spaces cause issues with Bungeecord's login cycle + // as usernames with spaces cause issues with Bungeecord's login cycle. + // However, this doesn't affect the final username as Floodgate is still in charge of that. + // So if you have (for example) replace spaces enabled on Floodgate the spaces will re-appear. String validUsername = username; if (remoteAuthType == AuthType.FLOODGATE) { validUsername = username.replace(' ', '_'); From 2f2e2cc28553162447e691aef83fb6f6c4661b9f Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 30 May 2021 21:36:25 -0400 Subject: [PATCH 054/107] Only upload the skin on Floodgate --- .../connector/network/session/GeyserSession.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 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 b30b181b6..1bf2c316b 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 @@ -732,6 +732,8 @@ public class GeyserSession implements CommandSender { address = handshakePacket.getHostname(); } + System.out.println((address + addressSuffix).getBytes(StandardCharsets.UTF_8).length); + event.setPacket(new HandshakePacket( handshakePacket.getProtocolVersion(), address + addressSuffix, @@ -802,9 +804,11 @@ public class GeyserSession implements CommandSender { SkinManager.handleBedrockSkin(playerEntity, clientData); } - // We'll send the skin upload a bit after the handshake packet (aka this packet), - // because otherwise the global server returns the data too fast. - getAuthData().upload(connector); + if (remoteAuthType == AuthType.FLOODGATE) { + // We'll send the skin upload a bit after the handshake packet (aka this packet), + // because otherwise the global server returns the data too fast. + getAuthData().upload(connector); + } } PacketTranslatorRegistry.JAVA_TRANSLATOR.translate(event.getPacket().getClass(), event.getPacket(), GeyserSession.this); From 5376ba49b822e10f30f1f8b15e57e47c180f8b6a Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 30 May 2021 21:37:32 -0400 Subject: [PATCH 055/107] ._. --- .../org/geysermc/connector/network/session/GeyserSession.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 1bf2c316b..ead9d1834 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 @@ -732,8 +732,6 @@ public class GeyserSession implements CommandSender { address = handshakePacket.getHostname(); } - System.out.println((address + addressSuffix).getBytes(StandardCharsets.UTF_8).length); - event.setPacket(new HandshakePacket( handshakePacket.getProtocolVersion(), address + addressSuffix, From 209a4ffc427b1bca2d2f313cdc6c8ec0a98f1545 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Mon, 31 May 2021 16:54:02 +0200 Subject: [PATCH 056/107] Use the correct plugin message packet. Thanks, Camo --- .../org/geysermc/connector/utils/PluginMessageUtils.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/utils/PluginMessageUtils.java b/connector/src/main/java/org/geysermc/connector/utils/PluginMessageUtils.java index 1569002f9..a914f699e 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/PluginMessageUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/PluginMessageUtils.java @@ -25,7 +25,7 @@ package org.geysermc.connector.utils; -import com.github.steveice10.mc.protocol.packet.ingame.server.ServerPluginMessagePacket; +import com.github.steveice10.mc.protocol.packet.ingame.client.ClientPluginMessagePacket; import com.google.common.base.Charsets; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; @@ -74,12 +74,7 @@ public class PluginMessageUtils { } public static void sendMessage(GeyserSession session, String channel, byte[] data) { - byte[] finalData = - ByteBuffer.allocate(data.length + getVarIntLength(data.length)) - .put(writeVarInt(data.length)) - .put(data) - .array(); - session.sendDownstreamPacket(new ServerPluginMessagePacket(channel, finalData)); + session.sendDownstreamPacket(new ClientPluginMessagePacket(channel, data)); } private static byte[] writeVarInt(int value) { From ebf726ce9efcdabfcb5c3bc078fb562c451cabee Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 1 Jun 2021 15:36:33 -0400 Subject: [PATCH 057/107] Yeet cache chunks So many features require this config option, and we don't intend on supporting it being both disabled and enabled. --- .../spigot/GeyserSpigotConfiguration.java | 5 -- .../configuration/GeyserConfiguration.java | 2 - .../GeyserJacksonConfiguration.java | 3 - .../connector/entity/FishingHookEntity.java | 7 +- .../connector/entity/LivingEntity.java | 8 +- .../connector/entity/ThrowableEntity.java | 7 +- .../living/merchant/VillagerEntity.java | 2 +- .../network/session/GeyserSession.java | 6 -- .../network/session/cache/ChunkCache.java | 6 +- .../player/BedrockActionTranslator.java | 73 +++++++++---------- .../collision/CollisionManager.java | 43 ++++------- .../collision/CollisionTranslator.java | 5 -- .../BeaconInventoryTranslator.java | 11 +-- .../JavaEntitySetPassengersTranslator.java | 4 +- .../player/JavaPlayerActionAckTranslator.java | 48 ------------ .../JavaPlayerPositionRotationTranslator.java | 15 ---- .../java/world/JavaBlockChangeTranslator.java | 3 +- .../world/JavaUpdateTileEntityTranslator.java | 22 +++--- .../entity/BannerBlockEntityTranslator.java | 5 -- .../entity/BedBlockEntityTranslator.java | 5 -- .../block/entity/BedrockOnlyBlockEntity.java | 9 ++- .../block/entity/BlockEntityTranslator.java | 19 ++--- .../CommandBlockBlockEntityTranslator.java | 5 -- .../DoubleChestBlockEntityTranslator.java | 2 +- .../FlowerPotBlockEntityTranslator.java | 2 +- .../NoteblockBlockEntityTranslator.java | 11 +-- .../block/entity/RequiresBlockState.java | 8 -- .../ShulkerBoxBlockEntityTranslator.java | 2 +- .../entity/SkullBlockEntityTranslator.java | 5 -- .../geysermc/connector/utils/ChunkUtils.java | 27 ++----- connector/src/main/resources/config.yml | 12 --- 31 files changed, 92 insertions(+), 290 deletions(-) diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotConfiguration.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotConfiguration.java index 2dbdbf830..45b1d2538 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotConfiguration.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotConfiguration.java @@ -48,9 +48,4 @@ public final class GeyserSpigotConfiguration extends GeyserJacksonConfiguration floodgateKeyPath = FloodgateKeyLoader.getKeyPath(this, floodgate, floodgateDataFolder, geyserDataFolder, plugin.getGeyserLogger()); } - - @Override - public boolean isCacheChunks() { - return true; // We override this as with Bukkit, we have direct access to the server implementation - } } 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 4ebc048fe..5b910dc68 100644 --- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java +++ b/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java @@ -87,8 +87,6 @@ public interface GeyserConfiguration { boolean isAboveBedrockNetherBuilding(); - boolean isCacheChunks(); - boolean isForceResourcePacks(); boolean isXboxAchievementsEnabled(); diff --git a/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java b/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java index 9a3024de7..5840561c6 100644 --- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java +++ b/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java @@ -111,9 +111,6 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration @JsonProperty("default-locale") private String defaultLocale = null; // is null by default so system language takes priority - @JsonProperty("cache-chunks") - private boolean cacheChunks = false; - @JsonProperty("cache-images") private int cacheImages = 0; 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 4f261ef39..db44a0767 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/FishingHookEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/FishingHookEntity.java @@ -174,11 +174,8 @@ public class FishingHookEntity extends ThrowableEntity { * @return true if this entity is currently in air. */ protected boolean isInAir(GeyserSession session) { - if (session.getConnector().getConfig().isCacheChunks()) { - int block = session.getConnector().getWorldManager().getBlockAt(session, position.toInt()); - return block == BlockTranslator.JAVA_AIR_ID; - } - return false; + int block = session.getConnector().getWorldManager().getBlockAt(session, position.toInt()); + return block == BlockTranslator.JAVA_AIR_ID; } @Override 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 f3e5661fe..66244da86 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/LivingEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/LivingEntity.java @@ -94,11 +94,9 @@ public class LivingEntity extends Entity { Position bedPosition = (Position) entityMetadata.getValue(); if (bedPosition != null) { metadata.put(EntityData.BED_POSITION, Vector3i.from(bedPosition.getX(), bedPosition.getY(), bedPosition.getZ())); - if (session.getConnector().getConfig().isCacheChunks()) { - int bed = session.getConnector().getWorldManager().getBlockAt(session, bedPosition); - // Bed has to be updated, or else player is floating in the air - ChunkUtils.updateBlock(session, bed, bedPosition); - } + int bed = session.getConnector().getWorldManager().getBlockAt(session, bedPosition); + // Bed has to be updated, or else player is floating in the air + ChunkUtils.updateBlock(session, bed, bedPosition); // Indicate that the player should enter the sleep cycle // Has to be a byte or it does not work // (Bed position is what actually triggers sleep - "pose" is only optional) diff --git a/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java b/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java index 9aea49656..1ba18bfd7 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java @@ -166,11 +166,8 @@ public class ThrowableEntity extends Entity implements Tickable { * @return true if this entity is currently in water. */ protected boolean isInWater(GeyserSession session) { - if (session.getConnector().getConfig().isCacheChunks()) { - int block = session.getConnector().getWorldManager().getBlockAt(session, position.toInt()); - return BlockStateValues.getWaterLevel(block) != -1; - } - return false; + int block = session.getConnector().getWorldManager().getBlockAt(session, position.toInt()); + return BlockStateValues.getWaterLevel(block) != -1; } @Override diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java index 6ab0107cc..1ba8b595b 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java @@ -111,7 +111,7 @@ public class VillagerEntity extends AbstractMerchantEntity { float bedPositionSubtractorW = 0; float bedPositionSubtractorN = 0; Vector3i bedPosition = metadata.getPos(EntityData.BED_POSITION, null); - if (session.getConnector().getConfig().isCacheChunks() && bedPosition != null) { + if (bedPosition != null) { bedId = session.getConnector().getWorldManager().getBlockAt(session, bedPosition); } String bedRotationZ = BlockTranslator.getJavaIdBlockMap().inverse().get(bedId); 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 454f03c8c..9f3e09381 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 @@ -233,12 +233,6 @@ public class GeyserSession implements CommandSender { @Setter private boolean sprinting; - /** - * Not updated if cache chunks is enabled. - */ - @Setter - private boolean jumping; - /** * Whether the player is swimming in water. * Used to update speed when crawling. 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 15009da21..a925b43b9 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 @@ -42,11 +42,7 @@ public class ChunkCache { private int minY; public ChunkCache(GeyserSession session) { - if (session.getConnector().getWorldManager().hasOwnChunkCache()) { - this.cache = false; // To prevent Spigot from initializing - } else { - this.cache = session.getConnector().getConfig().isCacheChunks(); - } + this.cache = !session.getConnector().getWorldManager().hasOwnChunkCache(); // To prevent Spigot from initializing chunks = cache ? new Long2ObjectOpenHashMap<>() : null; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java index c590176c0..d094bc353 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java @@ -51,8 +51,6 @@ import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.utils.BlockUtils; -import java.util.concurrent.TimeUnit; - @Translator(packet = PlayerActionPacket.class) public class BedrockActionTranslator extends PacketTranslator { @@ -162,43 +160,42 @@ public class BedrockActionTranslator extends PacketTranslator session.setJumping(false), 1, TimeUnit.SECONDS); - } + // Leaving as a potential placeholder for an event or soul sand fixing break; } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/collision/CollisionManager.java b/connector/src/main/java/org/geysermc/connector/network/translators/collision/CollisionManager.java index ff95593bc..b6d0ae176 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/collision/CollisionManager.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/collision/CollisionManager.java @@ -141,32 +141,22 @@ public class CollisionManager { Vector3d position = Vector3d.from(Double.parseDouble(Float.toString(bedrockPosition.getX())), javaY, Double.parseDouble(Float.toString(bedrockPosition.getZ()))); - if (session.getConnector().getConfig().isCacheChunks()) { - // With chunk caching, we can do some proper collision checks - updatePlayerBoundingBox(position); + updatePlayerBoundingBox(position); - // Correct player position - if (!correctPlayerPosition()) { - // Cancel the movement if it needs to be cancelled - recalculatePosition(); - return null; - } + // Correct player position + if (!correctPlayerPosition()) { + // Cancel the movement if it needs to be cancelled + recalculatePosition(); + return null; + } - position = Vector3d.from(playerBoundingBox.getMiddleX(), - playerBoundingBox.getMiddleY() - (playerBoundingBox.getSizeY() / 2), - playerBoundingBox.getMiddleZ()); + position = Vector3d.from(playerBoundingBox.getMiddleX(), + playerBoundingBox.getMiddleY() - (playerBoundingBox.getSizeY() / 2), + playerBoundingBox.getMiddleZ()); - if (!onGround) { - // Trim the position to prevent rounding errors that make Java think we are clipping into a block - position = Vector3d.from(position.getX(), Double.parseDouble(DECIMAL_FORMAT.format(position.getY())), position.getZ()); - } - } else { - // When chunk caching is off, we have to rely on this - // It rounds the Y position up to the nearest 0.5 - // This snaps players to snap to the top of stairs and slabs like on Java Edition - // However, it causes issues such as the player floating on carpets - if (onGround) javaY = Math.ceil(javaY * 2) / 2; - position = position.up(javaY - position.getY()); + if (!onGround) { + // Trim the position to prevent rounding errors that make Java think we are clipping into a block + position = Vector3d.from(position.getX(), Double.parseDouble(DECIMAL_FORMAT.format(position.getY())), position.getZ()); } return position; @@ -268,10 +258,6 @@ public class CollisionManager { * were they not sneaking */ public boolean isUnderSlab() { - if (!session.getConnector().getConfig().isCacheChunks()) { - // We can't reliably determine this - return false; - } Vector3i position = session.getPlayerEntity().getPosition().toInt(); BlockCollision collision = CollisionTranslator.getCollisionAt(session, position.getX(), position.getY(), position.getZ()); if (collision != null) { @@ -289,8 +275,7 @@ public class CollisionManager { * @return if the player is currently in a water block */ public boolean isPlayerInWater() { - return session.getConnector().getConfig().isCacheChunks() - && session.getConnector().getWorldManager().getBlockAt(session, session.getPlayerEntity().getPosition().toInt()) == BlockTranslator.JAVA_WATER_ID; + return session.getConnector().getWorldManager().getBlockAt(session, session.getPlayerEntity().getPosition().toInt()) == BlockTranslator.JAVA_WATER_ID; } /** diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/collision/CollisionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/collision/CollisionTranslator.java index 2be493214..e5cf52bc8 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/collision/CollisionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/collision/CollisionTranslator.java @@ -48,11 +48,6 @@ public class CollisionTranslator { private static final Int2ObjectMap COLLISION_MAP = new Int2ObjectOpenHashMap<>(); public static void init() { - // If chunk caching is off then don't initialize - if (!GeyserConnector.getInstance().getConfig().isCacheChunks()) { - return; - } - List> collisionTypes = new ArrayList<>(); Map, CollisionRemapper> annotationMap = new HashMap<>(); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BeaconInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BeaconInventoryTranslator.java index 5af921f2d..1d40db51f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BeaconInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BeaconInventoryTranslator.java @@ -54,15 +54,6 @@ import java.util.Collections; public class BeaconInventoryTranslator extends AbstractBlockInventoryTranslator { public BeaconInventoryTranslator() { super(1, new BlockInventoryHolder("minecraft:beacon", ContainerType.BEACON) { - @Override - public void prepareInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { - if (!session.getConnector().getConfig().isCacheChunks()) { - // Beacons cannot work without knowing their physical location - return; - } - super.prepareInventory(translator, session, inventory); - } - @Override protected boolean checkInteractionPosition(GeyserSession session) { // Since we can't fall back to a virtual inventory, let's make opening one easier @@ -71,7 +62,7 @@ public class BeaconInventoryTranslator extends AbstractBlockInventoryTranslator @Override public void openInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { - if (!session.getConnector().getConfig().isCacheChunks() || !((BeaconContainer) inventory).isUsingRealBlock()) { + if (!((BeaconContainer) inventory).isUsingRealBlock()) { InventoryUtils.closeInventory(session, inventory.getId(), false); return; } 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 9a092d088..23a7bcc4b 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 @@ -64,9 +64,7 @@ public class JavaEntitySetPassengersTranslator extends PacketTranslator 1.5 || (yDis < 1.45 || yDis > (session.isJumping() ? 4.3 : (session.isSprinting() ? 2.5 : 1.9))) || zDis > 1.5)) { - // Fake confirm the teleport but don't send it to the client - ClientTeleportConfirmPacket teleportConfirmPacket = new ClientTeleportConfirmPacket(packet.getTeleportId()); - session.sendDownstreamPacket(teleportConfirmPacket); - return; - } - } - // If coordinates are relative, then add to the existing coordinate double newX = packet.getX() + (packet.getRelative().contains(PositionElement.X) ? entity.getPosition().getX() : 0); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockChangeTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockChangeTranslator.java index de9563d9b..d5c54a5c7 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockChangeTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockChangeTranslator.java @@ -45,8 +45,7 @@ public class JavaBlockChangeTranslator extends PacketTranslator { - private final boolean cacheChunks; - - public JavaUpdateTileEntityTranslator() { - cacheChunks = GeyserConnector.getInstance().getConfig().isCacheChunks(); - } @Override public void translate(ServerUpdateTileEntityPacket packet, GeyserSession session) { String id = BlockEntityUtils.getBedrockBlockEntityId(packet.getType().name()); - if (packet.getNbt().isEmpty()) { // Fixes errors in CubeCraft sending empty NBT + if (packet.getNbt().isEmpty()) { // Fixes errors in servers sending empty NBT BlockEntityUtils.updateBlockEntity(session, null, packet.getPosition()); return; } @@ -59,11 +54,12 @@ public class JavaUpdateTileEntityTranslator extends PacketTranslator BLOCK_ENTITY_TRANSLATORS = new HashMap<>(); /** - * A list of all block entities that require the Java block state in order to fill out their block entity information. - * This list will be smaller with cache chunks on as we don't need to double-cache data + * A list of all block entities that only exist on Bedrock */ - public static final ObjectArrayList REQUIRES_BLOCK_STATE_LIST = new ObjectArrayList<>(); + public static final ObjectArrayList BEDROCK_ONLY_BLOCK_ENTITIES = new ObjectArrayList<>(); /** * Contains a list of irregular block entity name translations that can't be fit into the regex @@ -84,18 +83,12 @@ public abstract class BlockEntityTranslator { GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.network.translator.block_entity.failed", clazz.getCanonicalName())); } } - boolean cacheChunks = GeyserConnector.getInstance().getConfig().isCacheChunks(); - for (Class clazz : ref.getSubTypesOf(RequiresBlockState.class)) { - GeyserConnector.getInstance().getLogger().debug("Found block entity that requires block state: " + clazz.getCanonicalName()); + for (Class clazz : ref.getSubTypesOf(BedrockOnlyBlockEntity.class)) { + GeyserConnector.getInstance().getLogger().debug("Found Bedrock-only block entity: " + clazz.getCanonicalName()); try { - RequiresBlockState requiresBlockState = (RequiresBlockState) clazz.newInstance(); - if (cacheChunks && !(requiresBlockState instanceof BedrockOnlyBlockEntity)) { - // Not needed to put this one in the map; cache chunks takes care of that for us - GeyserConnector.getInstance().getLogger().debug("Not adding because cache chunks is enabled."); - continue; - } - REQUIRES_BLOCK_STATE_LIST.add(requiresBlockState); + BedrockOnlyBlockEntity bedrockOnlyBlockEntity = (BedrockOnlyBlockEntity) clazz.newInstance(); + BEDROCK_ONLY_BLOCK_ENTITIES.add(bedrockOnlyBlockEntity); } catch (InstantiationException | IllegalAccessException e) { GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.network.translator.block_state.failed", clazz.getCanonicalName())); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CommandBlockBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CommandBlockBlockEntityTranslator.java index a4bb3e691..08c108b78 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CommandBlockBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CommandBlockBlockEntityTranslator.java @@ -54,9 +54,4 @@ public class CommandBlockBlockEntityTranslator extends BlockEntityTranslator imp builder.put("LastExecution", (long) 0); } } - - @Override - public boolean isBlock(int blockState) { - return BlockStateValues.getCommandBlockValues().containsKey(blockState); - } } 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 9775017fb..7cda4f619 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 @@ -37,7 +37,7 @@ import org.geysermc.connector.utils.BlockEntityUtils; * Chests have more block entity properties in Bedrock, which is solved by implementing the BedrockOnlyBlockEntity */ @BlockEntity(name = "Chest") -public class DoubleChestBlockEntityTranslator extends BlockEntityTranslator implements BedrockOnlyBlockEntity, RequiresBlockState { +public class DoubleChestBlockEntityTranslator extends BlockEntityTranslator implements BedrockOnlyBlockEntity { @Override public boolean isBlock(int blockState) { return BlockStateValues.getDoubleChestValues().containsKey(blockState); 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 062fd4922..1fa6cab02 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 @@ -33,7 +33,7 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.block.BlockStateValues; import org.geysermc.connector.utils.BlockEntityUtils; -public class FlowerPotBlockEntityTranslator implements BedrockOnlyBlockEntity, RequiresBlockState { +public class FlowerPotBlockEntityTranslator implements BedrockOnlyBlockEntity { /** * @param blockState the Java block state of a potential flower pot block * @return true if the block is a flower pot diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/NoteblockBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/NoteblockBlockEntityTranslator.java index 3254565d1..a0c1033c5 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/NoteblockBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/NoteblockBlockEntityTranslator.java @@ -30,21 +30,14 @@ import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.packet.BlockEventPacket; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.block.BlockStateValues; -import org.geysermc.connector.utils.ChunkUtils; /** * Does not implement BlockEntityTranslator because it's only a block entity in Bedrock */ -public class NoteblockBlockEntityTranslator implements RequiresBlockState { - @Override - public boolean isBlock(int blockState) { - return BlockStateValues.getNoteblockPitch(blockState) != -1; - } +public class NoteblockBlockEntityTranslator { public static void translate(GeyserSession session, Position position) { - int blockState = session.getConnector().getConfig().isCacheChunks() ? - session.getConnector().getWorldManager().getBlockAt(session, position) : - ChunkUtils.CACHED_BLOCK_ENTITIES.removeInt(position); + int blockState = session.getConnector().getWorldManager().getBlockAt(session, position); BlockEventPacket blockEventPacket = new BlockEventPacket(); blockEventPacket.setBlockPosition(Vector3i.from(position.getX(), position.getY(), position.getZ())); blockEventPacket.setEventType(0); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/RequiresBlockState.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/RequiresBlockState.java index 146e024f2..76fb732b7 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/RequiresBlockState.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/RequiresBlockState.java @@ -29,12 +29,4 @@ package org.geysermc.connector.network.translators.world.block.entity; * Implemented in block entities if their Java block state is required for additional values in Bedrock */ public interface RequiresBlockState { - - /** - * Determines if block is part of class - * @param blockState BlockState to be compared - * @return true if part of the class - */ - boolean isBlock(int blockState); - } 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 04d58fcce..a6ea24f7c 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 @@ -32,7 +32,7 @@ import org.geysermc.connector.network.translators.world.block.BlockStateValues; import javax.annotation.Nullable; @BlockEntity(name = "ShulkerBox") -public class ShulkerBoxBlockEntityTranslator extends BlockEntityTranslator { +public class ShulkerBoxBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { /** * Also used in {@link org.geysermc.connector.network.translators.inventory.translators.ShulkerInventoryTranslator} * where {@code tag} is passed as null. 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 b3e25b212..e7139f20c 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 @@ -50,11 +50,6 @@ import java.util.concurrent.TimeUnit; public class SkullBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { public static boolean ALLOW_CUSTOM_SKULLS; - @Override - public boolean isBlock(int blockState) { - return BlockStateValues.getSkullVariant(blockState) != -1; - } - @Override public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { byte skullVariant = BlockStateValues.getSkullVariant(blockState); 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 b7370d1ff..39c47c829 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java @@ -42,8 +42,6 @@ import com.nukkitx.protocol.bedrock.packet.NetworkChunkPublisherUpdatePacket; import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; -import it.unimi.dsi.fastutil.objects.Object2IntMap; -import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import lombok.Data; import lombok.experimental.UtilityClass; import org.geysermc.connector.GeyserConnector; @@ -55,7 +53,6 @@ import org.geysermc.connector.network.translators.world.block.BlockStateValues; import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.network.translators.world.block.entity.BedrockOnlyBlockEntity; import org.geysermc.connector.network.translators.world.block.entity.BlockEntityTranslator; -import org.geysermc.connector.network.translators.world.block.entity.RequiresBlockState; import org.geysermc.connector.network.translators.world.block.entity.SkullBlockEntityTranslator; import org.geysermc.connector.network.translators.world.chunk.BlockStorage; import org.geysermc.connector.network.translators.world.chunk.ChunkSection; @@ -70,11 +67,6 @@ import static org.geysermc.connector.network.translators.world.block.BlockTransl @UtilityClass public class ChunkUtils { - /** - * Temporarily stores positions of BlockState values that are needed for certain block entities actively. - * Not used if cache chunks is enabled - */ - public static final Object2IntMap CACHED_BLOCK_ENTITIES = new Object2IntOpenHashMap<>(); private static int indexYZXtoXZY(int yzx) { return (yzx >> 8) | (yzx & 0x0F0) | ((yzx & 0x00F) << 8); @@ -364,20 +356,12 @@ public class ChunkUtils { return newLecternHasBook; }); - // Since Java stores bed colors/skull information as part of the namespaced ID and Bedrock stores it as a tag - // This is the only place I could find that interacts with the Java block state and block updates - // Iterates through all block entity translators and determines if the block state needs to be saved - for (RequiresBlockState requiresBlockState : BlockEntityTranslator.REQUIRES_BLOCK_STATE_LIST) { - if (requiresBlockState.isBlock(blockState)) { + // Iterates through all Bedrock-only block entity translators and determines if a manual block entity packet + // needs to be sent + for (BedrockOnlyBlockEntity bedrockOnlyBlockEntity : BlockEntityTranslator.BEDROCK_ONLY_BLOCK_ENTITIES) { + if (bedrockOnlyBlockEntity.isBlock(blockState)) { // Flower pots are block entities only in Bedrock and are not updated anywhere else like note blocks - if (requiresBlockState instanceof BedrockOnlyBlockEntity) { - ((BedrockOnlyBlockEntity) requiresBlockState).updateBlock(session, blockState, position); - break; - } - if (!session.getConnector().getConfig().isCacheChunks()) { - // Blocks aren't saved to a chunk cache; resort to this smaller cache - CACHED_BLOCK_ENTITIES.put(new Position(position.getX(), position.getY(), position.getZ()), blockState); - } + bedrockOnlyBlockEntity.updateBlock(session, blockState, position); break; //No block will be a part of two classes } } @@ -412,7 +396,6 @@ public class ChunkUtils { @Data public static final class ChunkData { private final ChunkSection[] sections; - private final NbtMap[] blockEntities; } } diff --git a/connector/src/main/resources/config.yml b/connector/src/main/resources/config.yml index 75070b8ee..20728ce2c 100644 --- a/connector/src/main/resources/config.yml +++ b/connector/src/main/resources/config.yml @@ -134,18 +134,6 @@ emote-offhand-workaround: "disabled" # 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. This feature does allow for a few things -# such as more accurate movement that causes less problems with anticheat (meaning -# you're less likely to be banned) and allows block break animations to show up in -# creative mode (and other features). Although this increases RAM usage, it likely -# won't have much of an effect for the vast majority of people. However, if you're -# running out of RAM or are in a RAM-sensitive environment, you may want to disable -# this. When using the Spigot version of Geyser, support for features or -# implementations this allows is automatically enabled without the additional caching -# as Geyser has direct access to the server itself. -cache-chunks: true - # Specify how many days images will be cached to disk to save downloading them from the internet. # A value of 0 is disabled. (Default: 0) cache-images: 0 From f09a32babc398042687c13b237569442b9ebcec6 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 1 Jun 2021 15:51:01 -0400 Subject: [PATCH 058/107] Add Adventure text library to README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index fba796903..c79482298 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ Any contributions are appreciated. Please feel free to reach out to us on [Disco you're interested in helping out with Geyser. ## Libraries Used: +- [Adventure Text Library](https://github.com/KyoriPowered/adventure) - [NukkitX Bedrock Protocol Library](https://github.com/NukkitX/Protocol) - [Steveice10's Java Protocol Library](https://github.com/Steveice10/MCProtocolLib) - [TerminalConsoleAppender](https://github.com/Minecrell/TerminalConsoleAppender) From f5c5d0cd3986d5757eeae6d2ddd85012e720c8e3 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 1 Jun 2021 21:12:58 -0400 Subject: [PATCH 059/107] Support 1.17-pre3 and Bedrock 1.17.0.58 --- README.md | 2 +- connector/pom.xml | 10 +- .../connector/network/BedrockProtocol.java | 4 +- .../network/UpstreamPacketHandler.java | 6 +- .../network/session/GeyserSession.java | 10 +- .../network/translators/item/ItemEntry.java | 4 +- .../translators/item/ItemRegistry.java | 35 +- .../world/block/BlockTranslator.java | 6 +- ...16_210.java => BlockTranslator1_17_0.java} | 8 +- .../geysermc/connector/utils/ChunkUtils.java | 14 +- .../resources/bedrock/biome_definitions.dat | Bin 37626 -> 39466 bytes .../bedrock/block_palette.1_17_0.nbt | Bin 0 -> 40439 bytes .../bedrock/blockpalette.1_16_210.nbt | Bin 36482 -> 0 bytes .../resources/bedrock/creative_items.json | 1849 +++++++++-------- .../resources/bedrock/entity_identifiers.dat | Bin 8775 -> 7382 bytes .../bedrock/runtime_item_states.json | 816 +++++--- connector/src/main/resources/mappings | 2 +- 17 files changed, 1577 insertions(+), 1189 deletions(-) rename connector/src/main/java/org/geysermc/connector/network/translators/world/block/{BlockTranslator1_16_210.java => BlockTranslator1_17_0.java} (85%) create mode 100644 connector/src/main/resources/bedrock/block_palette.1_17_0.nbt delete mode 100644 connector/src/main/resources/bedrock/blockpalette.1_16_210.nbt diff --git a/README.md b/README.md index c79482298..3af1b1e92 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have joined us here! -### Currently supporting Minecraft Bedrock v1.16.220.52 and Minecraft Java 21w20a. +### Currently supporting Minecraft Bedrock v1.17.0.58 and Minecraft Java 1.17-pre3. ## Setting Up Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set up Geyser. diff --git a/connector/pom.xml b/connector/pom.xml index 4aa6269b9..44cf15a14 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -31,8 +31,8 @@ com.github.CloudburstMC.Protocol - bedrock-v431 - 530a0e3 + bedrock-v440 + a8f4e93 compile @@ -120,9 +120,9 @@ compile - com.github.steveice10 - mcprotocollib - 1.17-pre1-SNAPSHOT + com.github.GeyserMC + MCProtocolLib + 9ba9d7e compile diff --git a/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java b/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java index 234b85884..84fc449e9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java +++ b/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java @@ -26,7 +26,7 @@ package org.geysermc.connector.network; import com.nukkitx.protocol.bedrock.BedrockPacketCodec; -import com.nukkitx.protocol.bedrock.v431.Bedrock_v431; +import com.nukkitx.protocol.bedrock.v440.Bedrock_v440; import java.util.ArrayList; import java.util.List; @@ -39,7 +39,7 @@ public class BedrockProtocol { * Default Bedrock codec that should act as a fallback. Should represent the latest available * release of the game that Geyser supports. */ - public static final BedrockPacketCodec DEFAULT_BEDROCK_CODEC = Bedrock_v431.V431_CODEC; + public static final BedrockPacketCodec DEFAULT_BEDROCK_CODEC = Bedrock_v440.V440_CODEC; /** * A list of all supported Bedrock versions that can join Geyser */ 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 17446effc..de5526d52 100644 --- a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java @@ -37,7 +37,7 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.cache.AdvancementsCache; import org.geysermc.connector.network.translators.PacketTranslatorRegistry; import org.geysermc.connector.network.translators.item.ItemRegistry; -import org.geysermc.connector.network.translators.world.block.BlockTranslator1_16_210; +import org.geysermc.connector.network.translators.world.block.BlockTranslator1_17_0; import org.geysermc.connector.utils.*; import java.io.FileInputStream; @@ -72,7 +72,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { session.getUpstream().getSession().setPacketCodec(packetCodec); // Set the block translation based off of version - session.setBlockTranslator(BlockTranslator1_16_210.INSTANCE); + session.setBlockTranslator(BlockTranslator1_17_0.INSTANCE); LoginEncryptionUtils.encryptPlayerConnection(connector, session, loginPacket); @@ -137,8 +137,6 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { stackPacket.getExperiments().add(new ExperimentData("data_driven_items", true)); } - stackPacket.getExperiments().add(new ExperimentData("caves_and_cliffs", true)); - session.sendUpstreamPacket(stackPacket); break; diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 9f3e09381..346fc18f5 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 @@ -512,12 +512,7 @@ public class GeyserSession implements CommandSender { upstream.sendPacket(entityPacket); CreativeContentPacket creativePacket = new CreativeContentPacket(); - if (upstream.getSession().getPacketCodec().getProtocolVersion() < Bedrock_v431.V431_CODEC.getProtocolVersion()) { - creativePacket.setContents(ItemRegistry.getPre1_16_220CreativeContents()); - } else { - // No additional work required - creativePacket.setContents(ItemRegistry.CREATIVE_ITEMS); - } + creativePacket.setContents(ItemRegistry.CREATIVE_ITEMS); upstream.sendPacket(creativePacket); PlayStatusPacket playStatusPacket = new PlayStatusPacket(); @@ -1048,14 +1043,13 @@ public class GeyserSession implements CommandSender { startGamePacket.setItemEntries(ItemRegistry.ITEMS); startGamePacket.setVanillaVersion("*"); startGamePacket.setInventoriesServerAuthoritative(true); + startGamePacket.setServerEngine(""); // Do we want to fill this in? SyncedPlayerMovementSettings settings = new SyncedPlayerMovementSettings(); settings.setMovementMode(AuthoritativeMovementMode.CLIENT); settings.setRewindHistorySize(0); settings.setServerAuthoritativeBlockBreaking(false); startGamePacket.setPlayerMovementSettings(settings); - - startGamePacket.getExperiments().add(new ExperimentData("caves_and_cliffs", true)); upstream.sendPacket(startGamePacket); } 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 2ac203e87..5acaa2e26 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 @@ -28,7 +28,7 @@ package org.geysermc.connector.network.translators.item; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.ToString; -import org.geysermc.connector.network.translators.world.block.BlockTranslator1_16_210; +import org.geysermc.connector.network.translators.world.block.BlockTranslator1_17_0; @Getter @AllArgsConstructor @@ -36,7 +36,7 @@ import org.geysermc.connector.network.translators.world.block.BlockTranslator1_1 public class ItemEntry { public static ItemEntry AIR = new ItemEntry("minecraft:air", "minecraft:air", 0, 0, 0, - BlockTranslator1_16_210.INSTANCE.getBedrockAirId(), 64); + BlockTranslator1_17_0.INSTANCE.getBedrockAirId(), 64); private final String javaIdentifier; private final String bedrockIdentifier; 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 0e1a28d54..558146d14 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 @@ -45,7 +45,7 @@ import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.translators.world.block.BlockTranslator; -import org.geysermc.connector.network.translators.world.block.BlockTranslator1_16_210; +import org.geysermc.connector.network.translators.world.block.BlockTranslator1_17_0; import org.geysermc.connector.utils.FileUtils; import org.geysermc.connector.utils.LanguageUtils; @@ -250,7 +250,7 @@ public class ItemRegistry { throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.toolbox.fail.runtime_java"), e); } - BlockTranslator blockTranslator = BlockTranslator1_16_210.INSTANCE; + BlockTranslator blockTranslator = BlockTranslator1_17_0.INSTANCE; int itemIndex = 0; int javaFurnaceMinecartId = 0; @@ -537,37 +537,6 @@ public class ItemRegistry { JAVA_ONLY_ITEMS = ImmutableSet.copyOf(javaOnlyItems); } - /* pre-1.16.220 support start */ - - private static ItemData[] LEGACY_CREATIVE_CONTENTS = null; - - /** - * Built on the fly so extra memory isn't used if there are no 1.16.210-or-below clients joining. - * - * @return a list of creative items built for versions before 1.16.220. - */ - public static ItemData[] getPre1_16_220CreativeContents() { - if (LEGACY_CREATIVE_CONTENTS != null) { - return LEGACY_CREATIVE_CONTENTS; - } - - // Pre-1.16.220 relies on item damage values that the creative content packet drops - ItemData[] creativeContents = new ItemData[CREATIVE_ITEMS.length]; - for (int i = 0; i < CREATIVE_ITEMS.length; i++) { - ItemData item = CREATIVE_ITEMS[i]; - if (item.getBlockRuntimeId() != 0) { - creativeContents[i] = item.toBuilder().damage(getItem(item).getBedrockData()).build(); - } else { - // No block runtime ID means that this item is backwards-compatible - creativeContents[i] = item; - } - } - LEGACY_CREATIVE_CONTENTS = creativeContents; - return creativeContents; - } - - /* pre-1.16.220 support end */ - /** * Gets an {@link ItemEntry} from the given {@link ItemStack}. * 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 efa3a7c2b..5f55809f1 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 @@ -43,9 +43,7 @@ import org.geysermc.connector.utils.FileUtils; import java.io.DataInputStream; import java.io.InputStream; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; +import java.util.*; import java.util.zip.GZIPInputStream; public abstract class BlockTranslator { @@ -221,7 +219,7 @@ public abstract class BlockTranslator { BlockMapping.AIR = JAVA_RUNTIME_ID_TO_BLOCK_MAPPING.get(JAVA_AIR_ID); - BlockTranslator1_16_210.init(); + BlockTranslator1_17_0.init(); BLOCKS_JSON = null; // We no longer require this so let it garbage collect away } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator1_16_210.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator1_17_0.java similarity index 85% rename from connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator1_16_210.java rename to connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator1_17_0.java index 58861cb9c..d86d0e71f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator1_16_210.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator1_17_0.java @@ -25,11 +25,11 @@ package org.geysermc.connector.network.translators.world.block; -public class BlockTranslator1_16_210 extends BlockTranslator { - public static final BlockTranslator1_16_210 INSTANCE = new BlockTranslator1_16_210(); +public class BlockTranslator1_17_0 extends BlockTranslator { + public static final BlockTranslator1_17_0 INSTANCE = new BlockTranslator1_17_0(); - public BlockTranslator1_16_210() { - super("bedrock/blockpalette.1_16_210.nbt"); + public BlockTranslator1_17_0() { + super("bedrock/block_palette.1_17_0.nbt"); } @Override 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 39c47c829..e88111fd4 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java @@ -74,17 +74,7 @@ public class ChunkUtils { public static ChunkData translateToBedrock(GeyserSession session, Column column) { Chunk[] javaSections = column.getChunks(); - - //FIXME TEMPORARY UNTIL THE CAVES AND CLIFFS EXPERIMENTAL DATA IS REMOVED UNLESS IT'S NOT REMOVED THEN HMMMM - int sectionYOffset; - if (session.getDimension().equals(DimensionUtils.OVERWORLD)) { - sectionYOffset = 4; - } else { - sectionYOffset = 0; - } - //FIXME END - - ChunkSection[] sections = new ChunkSection[javaSections.length + sectionYOffset]; + ChunkSection[] sections = new ChunkSection[javaSections.length]; // Temporarily stores compound tags of Bedrock-only block entities List bedrockOnlyBlockEntities = new ArrayList<>(); @@ -197,7 +187,7 @@ public class ChunkUtils { layers = new BlockStorage[]{ layer0, new BlockStorage(BitArrayVersion.V1.createArray(BlockStorage.SIZE, layer1Data), layer1Palette) }; } - sections[sectionY + sectionYOffset] = new ChunkSection(layers); + sections[sectionY] = new ChunkSection(layers); } CompoundTag[] blockEntities = column.getTileEntities(); diff --git a/connector/src/main/resources/bedrock/biome_definitions.dat b/connector/src/main/resources/bedrock/biome_definitions.dat index 6d72cc924919f5e018c677c9e3a2def659f07f6d..8fe3b95a585f8fde3f824ee822e55b68a3421fb1 100644 GIT binary patch literal 39466 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 VAIS)7nt6Z^COkccFUEfK{{iHuH@W}- literal 0 HcmV?d00001 diff --git a/connector/src/main/resources/bedrock/blockpalette.1_16_210.nbt b/connector/src/main/resources/bedrock/blockpalette.1_16_210.nbt deleted file mode 100644 index aef6203a94d5ca6fc12c9b8740fa254fada60348..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 36482 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 literal 8775 zcmbuF@2f3S7{~W^r0e+2bzRqWyGfEH-6Y*4Ns=T143B=5+GQaIj;z7+?M2P=Zyl`^IX_@EiBG- zJXQtJ_U`_SrA`5~p9wmrRaIq5m4G_HM4jEDrq+yrbTA|hR(PVx;Q1|S+Y>-L6w+2X ztf5`d(sFC7%vqZQ?l2RF5qq@O37{@xqQGBHMv+FdUe08J>!ZN+SA-x=@SDI%hqB)2 z*uEFHw~fAbwh*Z`3d4V8D6co05HlMqV^Q4IOctavv#!GOzGn9*YGSzrR~%)cAY!F8 z(uqh(2fQ1YJgBC$mM+l}J5R)?|2kt>HZjn>%Ga7N#SQ+IlX5wL{PN?|UVjKFgBd@ncLEfg#Bdx@e zwtGKQ9@@Abjg;#I@w<-7UblerD2Op8BOXd;y~w2nY>zvt?L!Jb%CW*TWX?Os`8 zi?P}OW!ExUP!f3^EH6ixr2Vabw~Z^h9rHRS6UuDMbY0^0A(3O_%$Brtibi%gz&_@t z7rZjp2^qPWsSwWW3%^X>CtP{pzSz+Mr*KrLfVsZ6T}IFo#=P$2QMtoQcTTc)TnzpK*_UQ48PlH*MCk#b|mIru5?;R1LUU}eKEq%(M~7axRUjg&4c%Bl=!;w^)bZ>t zILmovEnd}M;v(VZI&TVMq@9Y$Q6ThX2DOPO43*il#1vSsa9OZuRfU~)q`k_;K?Tz5 zItY7>L26PA!b+(NPw4>M>s%Z-#?uq)v=E*i@9EJWpwDrkpm9lF-(=vrl~R>PR+%WR z*TE!ti@|Kd2<>`aeI>otq5Ixu5F3>+*o~Tr92ddM*gFhhBN6KE|JNrfW=PDtTqN9_ z5GfK_(5$z01@QM7@FoB-up%40WxvNofh&zpGhZ}=#u{|x2Mp*!bIhsLEbBQRa-o>@ z*#q+I&Vwqy-~umc2aa?~3Po>lu@|wiHEt6hak&Rt?rdz+r#Vubu2tV~mBFoLHd<}a%)jl>dJ_u?D9RWT zFk+q(b}|}+rA Date: Tue, 1 Jun 2021 22:14:21 -0400 Subject: [PATCH 060/107] Initial goat implementation --- .../entity/living/animal/GoatEntity.java | 44 +++++++++++++++++++ .../connector/entity/type/EntityType.java | 3 +- 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 connector/src/main/java/org/geysermc/connector/entity/living/animal/GoatEntity.java diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/GoatEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/GoatEntity.java new file mode 100644 index 000000000..00e163496 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/GoatEntity.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.entity.living.animal; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class GoatEntity extends AnimalEntity { + public GoatEntity(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); + + + } +} 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 0e9c2fe9b..e312d10b3 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 @@ -161,7 +161,8 @@ public enum EntityType { ZOGLIN(ZoglinEntity.class, 126, 1.4f, 1.3965f, 1.3965f, 0f, "minecraft:zoglin"), PIGLIN(PiglinEntity.class, 123, 1.95f, 0.6f, 0.6f, 0f, "minecraft:piglin"), PIGLIN_BRUTE(BasePiglinEntity.class, 127, 1.95f, 0.6f, 0.6f, 0f, "minecraft:piglin_brute"), - //TODO: GOAT AXOLOTL GLOW_SQUID GLOW_ITEM_FRAME MARKER + //TODO: AXOLOTL GLOW_SQUID GLOW_ITEM_FRAME MARKER + GOAT(GoatEntity.class, 0, 1.3f, 0.9f, 0.9f, 0f, "minecraft:goat"), /** * Item frames are handled differently since they are a block in Bedrock. From 93cc2d21361296eda864987266693f7756f5d357 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 1 Jun 2021 22:27:09 -0400 Subject: [PATCH 061/107] Fix sound --- connector/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/pom.xml b/connector/pom.xml index 44cf15a14..997a83f76 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -122,7 +122,7 @@ com.github.GeyserMC MCProtocolLib - 9ba9d7e + ba26c10 compile From d6bee02aa9131786f0a300cfdcf99eece1a84d66 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 2 Jun 2021 22:06:20 -0400 Subject: [PATCH 062/107] Update to 1.17-pre4 and fix Bedrock encryption on Java 16 --- connector/pom.xml | 7 +++++-- .../java/world/JavaSpawnParticleTranslator.java | 3 ++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/connector/pom.xml b/connector/pom.xml index 997a83f76..49665c89f 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -32,7 +32,7 @@ com.github.CloudburstMC.Protocol bedrock-v440 - a8f4e93 + 1656151 compile @@ -122,7 +122,10 @@ com.github.GeyserMC MCProtocolLib - ba26c10 + 5ff383d27aafd081d929fe61a7c46fb03d5e7537 + + + compile diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaSpawnParticleTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaSpawnParticleTranslator.java index dabcfd4c1..77f00bb1e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaSpawnParticleTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaSpawnParticleTranslator.java @@ -114,7 +114,8 @@ public class JavaSpawnParticleTranslator extends PacketTranslator Date: Wed, 2 Jun 2021 22:54:17 -0400 Subject: [PATCH 063/107] Initial glow squid implementation --- .../entity/living/GlowSquidEntity.java | 43 +++++++++++++++++++ .../connector/entity/living/SquidEntity.java | 1 - .../connector/entity/type/EntityType.java | 4 +- 3 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/entity/living/GlowSquidEntity.java diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/GlowSquidEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/GlowSquidEntity.java new file mode 100644 index 000000000..b68c5d224 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/GlowSquidEntity.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.entity.living; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class GlowSquidEntity extends SquidEntity { + public GlowSquidEntity(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); + // TODO "dark ticks remaining" ??? does this have a Bedrock equivalent? + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/SquidEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/SquidEntity.java index d4a409242..df914162d 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/SquidEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/SquidEntity.java @@ -29,7 +29,6 @@ import com.nukkitx.math.vector.Vector3f; import org.geysermc.connector.entity.type.EntityType; public class SquidEntity extends WaterEntity { - public SquidEntity(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 e312d10b3..f99484cf0 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 @@ -161,8 +161,10 @@ public enum EntityType { ZOGLIN(ZoglinEntity.class, 126, 1.4f, 1.3965f, 1.3965f, 0f, "minecraft:zoglin"), PIGLIN(PiglinEntity.class, 123, 1.95f, 0.6f, 0.6f, 0f, "minecraft:piglin"), PIGLIN_BRUTE(BasePiglinEntity.class, 127, 1.95f, 0.6f, 0.6f, 0f, "minecraft:piglin_brute"), - //TODO: AXOLOTL GLOW_SQUID GLOW_ITEM_FRAME MARKER + //TODO: AXOLOTL GLOW_ITEM_FRAME + GLOW_SQUID(GlowSquidEntity.class, 0, 0.8f, 0.8f, 0.8f, 0f, "minecraft:glow_squid"), GOAT(GoatEntity.class, 0, 1.3f, 0.9f, 0.9f, 0f, "minecraft:goat"), + MARKER(Entity.class, 0, 0, 0, 0, 0, "minecraft:marker"), // Only should be used for ALL_JAVA_IDENTIFIERS /** * Item frames are handled differently since they are a block in Bedrock. From 33d3329f8fd82db816dc11e5637704d6ac8ae122 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 2 Jun 2021 23:13:14 -0400 Subject: [PATCH 064/107] Update small particle mapping change --- 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 07bd1db23..f02ef695b 160000 --- a/connector/src/main/resources/mappings +++ b/connector/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit 07bd1db239492e22214abd911696b8cb99b0fe28 +Subproject commit f02ef695b375338641b94998cee8feda638ada0b From 760777000a805d9b2f2cf66bddb798731e1bb7b3 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 3 Jun 2021 16:59:53 -0400 Subject: [PATCH 065/107] Bump for 1.17-pre5 --- connector/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/connector/pom.xml b/connector/pom.xml index 49665c89f..55b5d171e 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -122,10 +122,10 @@ com.github.GeyserMC MCProtocolLib - 5ff383d27aafd081d929fe61a7c46fb03d5e7537 + 5f523d3 - + compile From 5b7aa574723bb38d0cef0c67f3db96528e0d21fc Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 3 Jun 2021 18:34:30 -0400 Subject: [PATCH 066/107] Implement axolotl --- .../entity/living/animal/AxolotlEntity.java | 65 +++++++++++++++++++ .../connector/entity/type/EntityType.java | 1 + 2 files changed, 66 insertions(+) create mode 100644 connector/src/main/java/org/geysermc/connector/entity/living/animal/AxolotlEntity.java diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/AxolotlEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/AxolotlEntity.java new file mode 100644 index 000000000..7c6c81ba2 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/AxolotlEntity.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.entity.living.animal; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.item.ItemEntry; + +public class AxolotlEntity extends AnimalEntity { + public AxolotlEntity(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); + if (entityMetadata.getId() == 17) { + int variant = (int) entityMetadata.getValue(); + switch (variant) { + case 1: // Java - "Wild" (brown) + variant = 3; + break; + case 3: // Java - cyan + variant = 1; + break; + } + metadata.put(EntityData.VARIANT, variant); + } + else if (entityMetadata.getId() == 18) { + metadata.getFlags().setFlag(EntityFlag.PLAYING_DEAD, (boolean) entityMetadata.getValue()); + } + } + + @Override + public boolean canEat(GeyserSession session, String javaIdentifierStripped, ItemEntry itemEntry) { + return javaIdentifierStripped.equals("tropical_fish_bucket"); + } +} 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 f99484cf0..e6a70f24d 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 @@ -162,6 +162,7 @@ public enum EntityType { PIGLIN(PiglinEntity.class, 123, 1.95f, 0.6f, 0.6f, 0f, "minecraft:piglin"), PIGLIN_BRUTE(BasePiglinEntity.class, 127, 1.95f, 0.6f, 0.6f, 0f, "minecraft:piglin_brute"), //TODO: AXOLOTL GLOW_ITEM_FRAME + AXOLOTL(AxolotlEntity.class, 0, 0.42f, 0.7f, 0.7f, 0f, "minecraft:axolotl"), GLOW_SQUID(GlowSquidEntity.class, 0, 0.8f, 0.8f, 0.8f, 0f, "minecraft:glow_squid"), GOAT(GoatEntity.class, 0, 1.3f, 0.9f, 0.9f, 0f, "minecraft:goat"), MARKER(Entity.class, 0, 0, 0, 0, 0, "minecraft:marker"), // Only should be used for ALL_JAVA_IDENTIFIERS From 1fe179c6d22ab3d64ef91a83a9f26c50a550f05d Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 3 Jun 2021 19:04:28 -0400 Subject: [PATCH 067/107] Implement glow item frames --- connector/pom.xml | 2 +- .../java/org/geysermc/connector/entity/ItemFrameEntity.java | 4 ++-- .../java/org/geysermc/connector/entity/type/EntityType.java | 2 +- .../bedrock/BedrockBlockPickRequestTranslator.java | 3 ++- .../bedrock/BedrockEntityPickRequestTranslator.java | 1 + .../java/entity/spawn/JavaSpawnEntityTranslator.java | 2 +- .../network/translators/world/block/BlockTranslator.java | 3 ++- .../main/java/org/geysermc/connector/utils/ChunkUtils.java | 2 ++ 8 files changed, 12 insertions(+), 7 deletions(-) diff --git a/connector/pom.xml b/connector/pom.xml index 55b5d171e..bf1a8902a 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -122,7 +122,7 @@ com.github.GeyserMC MCProtocolLib - 5f523d3 + eb02688 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 ead2b3b3d..f2f36e606 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java @@ -84,7 +84,7 @@ public class ItemFrameEntity extends Entity { @Override public void spawnEntity(GeyserSession session) { NbtMapBuilder blockBuilder = NbtMap.builder() - .putString("name", "minecraft:frame") + .putString("name", this.entityType == EntityType.GLOW_ITEM_FRAME ? "minecraft:glow_frame" : "minecraft:frame") .putInt("version", session.getBlockTranslator().getBlockStateVersion()); blockBuilder.put("states", NbtMap.builder() .putInt("facing_direction", direction.ordinal()) @@ -167,7 +167,7 @@ public class ItemFrameEntity extends Entity { builder.putInt("y", bedrockPosition.getY()); builder.putInt("z", bedrockPosition.getZ()); builder.putByte("isMovable", (byte) 1); - builder.putString("id", "ItemFrame"); + builder.putString("id", this.entityType == EntityType.GLOW_ITEM_FRAME ? "GlowItemFrame" : "ItemFrame"); return builder.build(); } 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 e6a70f24d..b4292c608 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 @@ -161,7 +161,6 @@ public enum EntityType { ZOGLIN(ZoglinEntity.class, 126, 1.4f, 1.3965f, 1.3965f, 0f, "minecraft:zoglin"), PIGLIN(PiglinEntity.class, 123, 1.95f, 0.6f, 0.6f, 0f, "minecraft:piglin"), PIGLIN_BRUTE(BasePiglinEntity.class, 127, 1.95f, 0.6f, 0.6f, 0f, "minecraft:piglin_brute"), - //TODO: AXOLOTL GLOW_ITEM_FRAME AXOLOTL(AxolotlEntity.class, 0, 0.42f, 0.7f, 0.7f, 0f, "minecraft:axolotl"), GLOW_SQUID(GlowSquidEntity.class, 0, 0.8f, 0.8f, 0.8f, 0f, "minecraft:glow_squid"), GOAT(GoatEntity.class, 0, 1.3f, 0.9f, 0.9f, 0f, "minecraft:goat"), @@ -171,6 +170,7 @@ public enum EntityType { * Item frames are handled differently since they are a block in Bedrock. */ ITEM_FRAME(ItemFrameEntity.class, 0, 0, 0), + GLOW_ITEM_FRAME(ItemFrameEntity.class, 0, 0, 0), /** * Not an entity in Bedrock, so we replace it with an evoker diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestTranslator.java index ba74c7769..493789bbb 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestTranslator.java @@ -28,6 +28,7 @@ package org.geysermc.connector.network.translators.bedrock; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.packet.BlockPickRequestPacket; import org.geysermc.connector.entity.ItemFrameEntity; +import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; @@ -53,7 +54,7 @@ public class BedrockBlockPickRequestTranslator extends PacketTranslator entry : blockStateOrderedMap.object2IntEntrySet()) { - if (entry.getKey().getString("name").equals("minecraft:frame")) { + String name = entry.getKey().getString("name"); + if (name.equals("minecraft:frame") || name.equals("minecraft:glow_frame")) { itemFrames.put(entry.getKey(), entry.getIntValue()); } } 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 e88111fd4..3fd38d508 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java @@ -290,6 +290,8 @@ public class ChunkUtils { if (itemFrameEntity != null) { if (blockState == JAVA_AIR_ID) { // Item frame is still present and no block overrides that; refresh it itemFrameEntity.updateBlock(session); + // Still update the chunk cache with the new block + session.getChunkCache().updateBlock(position.getX(), position.getY(), position.getZ(), blockState); return; } // Otherwise, let's still store our reference to the item frame, but let the new block take precedence for now From c2be67bc3d22100cb7a25bbb7925676b812e1945 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 4 Jun 2021 21:28:21 -0400 Subject: [PATCH 068/107] Update to 1.17-rc1 --- connector/pom.xml | 4 ++-- .../src/main/java/org/geysermc/connector/GeyserConnector.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/connector/pom.xml b/connector/pom.xml index bf1a8902a..742e792d8 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -122,10 +122,10 @@ com.github.GeyserMC MCProtocolLib - eb02688 + bc06ae5 - + compile diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 3f82889f5..89e7a06d7 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -106,7 +106,7 @@ public class GeyserConnector { private final ScheduledExecutorService generalThreadPool; - private BedrockServer bedrockServer; + private final BedrockServer bedrockServer; private final PlatformType platformType; private final GeyserBootstrap bootstrap; From 249f04441dedbf4fb480d9fd6cf49f90d1afd788 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 4 Jun 2021 22:26:29 -0400 Subject: [PATCH 069/107] Set entity dimensions for goat long jumping --- .../org/geysermc/connector/entity/Entity.java | 35 ++++++------------- .../connector/entity/LivingEntity.java | 11 ++++++ .../entity/living/animal/GoatEntity.java | 14 ++++++++ .../connector/entity/player/PlayerEntity.java | 21 +++++++++++ 4 files changed, 56 insertions(+), 25 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 bdd01ee4e..505278653 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/Entity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/Entity.java @@ -303,31 +303,7 @@ public class Entity { metadata.getFlags().setFlag(EntityFlag.SLEEPING, pose.equals(Pose.SLEEPING)); // Triggered when crawling metadata.getFlags().setFlag(EntityFlag.SWIMMING, pose.equals(Pose.SWIMMING)); - float width = entityType.getWidth(); - float height = entityType.getHeight(); - switch (pose) { - case SLEEPING: - if (this instanceof LivingEntity) { - width = 0.2f; - height = 0.2f; - } - break; - case SNEAKING: - if (entityType == EntityType.PLAYER) { - height = 1.5f; - } - break; - case FALL_FLYING: - case SPIN_ATTACK: - case SWIMMING: - if (entityType == EntityType.PLAYER) { - // Seems like this is only cared about for players; nothing else - height = 0.6f; - } - break; - } - metadata.put(EntityData.BOUNDING_BOX_WIDTH, width); - metadata.put(EntityData.BOUNDING_BOX_HEIGHT, height); + setDimensions(pose); break; case 7: //TODO check @@ -349,6 +325,15 @@ public class Entity { session.sendUpstreamPacket(entityDataPacket); } + /** + * Set the height and width of the entity's bounding box + */ + protected void setDimensions(Pose pose) { + // No flexibility options for basic entities + metadata.put(EntityData.BOUNDING_BOX_WIDTH, entityType.getWidth()); + metadata.put(EntityData.BOUNDING_BOX_HEIGHT, entityType.getHeight()); + } + /** * x = Pitch, y = HeadYaw, z = Yaw * 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 66244da86..9ccb91fc1 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/LivingEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/LivingEntity.java @@ -26,6 +26,7 @@ package org.geysermc.connector.entity; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3i; @@ -111,6 +112,16 @@ public class LivingEntity extends Entity { super.updateBedrockMetadata(entityMetadata, session); } + @Override + protected void setDimensions(Pose pose) { + if (pose == Pose.SLEEPING) { + metadata.put(EntityData.BOUNDING_BOX_WIDTH, 0.2f); + metadata.put(EntityData.BOUNDING_BOX_HEIGHT, 0.2f); + } else { + super.setDimensions(pose); + } + } + public void updateAllEquipment(GeyserSession session) { if (!valid) return; diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/GoatEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/GoatEntity.java index 00e163496..e0c5066d6 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/GoatEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/GoatEntity.java @@ -26,11 +26,16 @@ package org.geysermc.connector.entity.living.animal; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose; 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 GoatEntity extends AnimalEntity { + private static final float LONG_JUMPING_HEIGHT = 1.3f * 0.7f; + private static final float LONG_JUMPING_WIDTH = 0.9f * 0.7f; + public GoatEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); } @@ -39,6 +44,15 @@ public class GoatEntity extends AnimalEntity { public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { super.updateBedrockMetadata(entityMetadata, session); + } + @Override + protected void setDimensions(Pose pose) { + if (pose == Pose.LONG_JUMPING) { + metadata.put(EntityData.BOUNDING_BOX_WIDTH, LONG_JUMPING_WIDTH); + metadata.put(EntityData.BOUNDING_BOX_HEIGHT, LONG_JUMPING_HEIGHT); + } else { + super.setDimensions(pose); + } } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/player/PlayerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/player/PlayerEntity.java index f057438cb..3cda303cd 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/player/PlayerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/player/PlayerEntity.java @@ -27,6 +27,7 @@ package org.geysermc.connector.entity.player; import com.github.steveice10.mc.auth.data.GameProfile; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3i; @@ -334,6 +335,26 @@ public class PlayerEntity extends LivingEntity { } } + @Override + protected void setDimensions(Pose pose) { + float height; + switch (pose) { + case SNEAKING: + height = 1.5f; + break; + case FALL_FLYING: + case SPIN_ATTACK: + case SWIMMING: + height = 0.6f; + break; + default: + super.setDimensions(pose); + return; + } + metadata.put(EntityData.BOUNDING_BOX_WIDTH, entityType.getWidth()); + metadata.put(EntityData.BOUNDING_BOX_HEIGHT, height); + } + @Override public void updateBedrockAttributes(GeyserSession session) { // TODO: Don't use duplicated code if (!valid) return; From 282800cefc8870712bfc304aa6cd23dc37ea3617 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 5 Jun 2021 13:36:47 -0400 Subject: [PATCH 070/107] 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 f02ef695b..bcb49d0e4 160000 --- a/connector/src/main/resources/mappings +++ b/connector/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit f02ef695b375338641b94998cee8feda638ada0b +Subproject commit bcb49d0e463dcac2ad78e3153d3281915fbbbcb2 From e4e35d3c0b68fbb515a20cd34b09d1e2797a9914 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 5 Jun 2021 13:51:02 -0400 Subject: [PATCH 071/107] Fix llama carpet decoration offset --- .../connector/network/translators/item/ItemRegistry.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 558146d14..5d5b5f569 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 @@ -456,7 +456,7 @@ public class ItemRegistry { BOATS.add(entry.getValue().get("bedrock_id").intValue()); } else if (entry.getKey().contains("bucket") && !entry.getKey().contains("milk")) { BUCKETS.add(entry.getValue().get("bedrock_id").intValue()); - } else if (entry.getKey().contains("_carpet")) { + } else if (entry.getKey().contains("_carpet") && !entry.getKey().contains("moss")) { // This should be the numerical order Java sends as an integer value for llamas CARPETS.add(ItemData.builder() .id(itemEntry.getBedrockId()) From 50b51f5f5755b3c26c77ffbf5a47f62f1ba1a8e5 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sat, 5 Jun 2021 23:12:33 +0200 Subject: [PATCH 072/107] Added the initial version of news --- .../org/geysermc/floodgate/news/NewsItem.java | 139 ++++++++++++++ .../floodgate/news/NewsItemAction.java | 44 +++++ .../floodgate/news/NewsItemMessage.java | 89 +++++++++ .../org/geysermc/floodgate/news/NewsType.java | 59 ++++++ .../news/data/BuildSpecificData.java | 60 ++++++ .../floodgate/news/data/CheckAfterData.java | 42 +++++ .../floodgate/news/data/ItemData.java | 29 +++ .../geysermc/floodgate/time/TimeSyncer.java | 7 +- .../floodgate/util/WebsocketEventType.java | 64 ++++++- .../geysermc/connector/GeyserConnector.java | 20 ++ .../connector/skin/FloodgateSkinUploader.java | 6 +- .../geysermc/connector/utils/Constants.java | 18 +- .../geysermc/connector/utils/NewsHandler.java | 175 ++++++++++++++++++ .../geysermc/connector/utils/WebUtils.java | 3 +- 14 files changed, 735 insertions(+), 20 deletions(-) create mode 100644 common/src/main/java/org/geysermc/floodgate/news/NewsItem.java create mode 100644 common/src/main/java/org/geysermc/floodgate/news/NewsItemAction.java create mode 100644 common/src/main/java/org/geysermc/floodgate/news/NewsItemMessage.java create mode 100644 common/src/main/java/org/geysermc/floodgate/news/NewsType.java create mode 100644 common/src/main/java/org/geysermc/floodgate/news/data/BuildSpecificData.java create mode 100644 common/src/main/java/org/geysermc/floodgate/news/data/CheckAfterData.java create mode 100644 common/src/main/java/org/geysermc/floodgate/news/data/ItemData.java create mode 100644 connector/src/main/java/org/geysermc/connector/utils/NewsHandler.java diff --git a/common/src/main/java/org/geysermc/floodgate/news/NewsItem.java b/common/src/main/java/org/geysermc/floodgate/news/NewsItem.java new file mode 100644 index 000000000..2f6bb4852 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/news/NewsItem.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.floodgate.news; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import org.geysermc.floodgate.news.data.ItemData; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +public final class NewsItem { + private final int id; + private final String project; + private final boolean active; + private final NewsType type; + private final ItemData data; + private final boolean priority; + private final String message; + private final Set actions; + private final String url; + + private NewsItem(int id, String project, boolean active, NewsType type, ItemData data, + boolean priority, String message, Set actions, String url) { + this.id = id; + this.project = project; + this.active = active; + this.type = type; + this.data = data; + this.priority = priority; + this.message = message; + this.actions = Collections.unmodifiableSet(actions); + this.url = url; + } + + public static NewsItem readItem(JsonObject newsItem) { + NewsType newsType = NewsType.getByName(newsItem.get("type").getAsString()); + if (newsType == null) { + return null; + } + + JsonObject messageObject = newsItem.getAsJsonObject("message"); + NewsItemMessage itemMessage = NewsItemMessage.getById(messageObject.get("id").getAsInt()); + + String message = "Received an unknown news message type. Please update"; + if (itemMessage != null) { + message = itemMessage.getFormattedMessage(messageObject.getAsJsonArray("args")); + } + + Set actions = new HashSet<>(); + for (JsonElement actionElement : newsItem.getAsJsonArray("actions")) { + NewsItemAction action = NewsItemAction.getByName(actionElement.getAsString()); + if (action != null) { + actions.add(action); + } + } + + return new NewsItem( + newsItem.get("id").getAsInt(), + newsItem.get("project").getAsString(), + newsItem.get("active").getAsBoolean(), + newsType, + newsType.read(newsItem.getAsJsonObject("data")), + newsItem.get("priority").getAsBoolean(), + message, + actions, + newsItem.get("url").getAsString() + ); + } + + public int getId() { + return id; + } + + public String getProject() { + return project; + } + + public boolean isActive() { + return active; + } + + public NewsType getType() { + return type; + } + + public ItemData getData() { + return data; + } + + @SuppressWarnings("unchecked") + public T getDataAs(Class type) { + return (T) data; + } + + public boolean isPriority() { + return priority; + } + + public String getRawMessage() { + return message; + } + + public String getMessage() { + return message + " See " + getUrl() + " for more information."; + } + + public Set getActions() { + return actions; + } + + public String getUrl() { + return url; + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/news/NewsItemAction.java b/common/src/main/java/org/geysermc/floodgate/news/NewsItemAction.java new file mode 100644 index 000000000..78a8e4ed3 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/news/NewsItemAction.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.floodgate.news; + +public enum NewsItemAction { + ON_SERVER_STARTED, + ON_OPERATOR_JOIN, + BROADCAST_TO_CONSOLE, + BROADCAST_TO_OPERATORS; + + private static final NewsItemAction[] VALUES = values(); + + public static NewsItemAction getByName(String actionName) { + for (NewsItemAction type : VALUES) { + if (type.name().equalsIgnoreCase(actionName)) { + return type; + } + } + return null; + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/news/NewsItemMessage.java b/common/src/main/java/org/geysermc/floodgate/news/NewsItemMessage.java new file mode 100644 index 000000000..b11605fb4 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/news/NewsItemMessage.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.floodgate.news; + +import com.google.gson.JsonArray; + +// {} is used for things that have to be filled in by the server, +// {@} is for things that have to be filled in by us +public enum NewsItemMessage { + UPDATE_AVAILABLE("There is an update available for {}. The newest version is: {}"), + UPDATE_RECOMMENDED(UPDATE_AVAILABLE + ". Your version is quite old, updating is recommend."), + UPDATE_HIGHLY_RECOMMENDED(UPDATE_AVAILABLE + ". We highly recommend updating because some important changes have been made."), + UPDATE_ANCIENT_VERSION(UPDATE_AVAILABLE + ". You are running an ancient version, updating is recommended."), + + DOWNTIME_GENERIC("The {} is temporarily going down for maintenance soon."), + DOWNTIME_WITH_START("The {} is temporarily going down for maintenance on {}."), + DOWNTIME_TIMEFRAME(DOWNTIME_WITH_START + " The maintenance is expected to last till {}."); + + private static final NewsItemMessage[] VALUES = values(); + + private final String messageFormat; + private final String[] messageSplitted; + + NewsItemMessage(String messageFormat) { + this.messageFormat = messageFormat; + this.messageSplitted = messageFormat.split(" "); + } + + public static NewsItemMessage getById(int id) { + return VALUES.length > id ? VALUES[id] : null; + } + + public String getMessageFormat() { + return messageFormat; + } + + public String getFormattedMessage(JsonArray serverArguments) { + int serverArgumentsIndex = 0; + + StringBuilder message = new StringBuilder(); + for (String split : messageSplitted) { + if (message.length() > 0) { + message.append(' '); + } + + String result = split; + + if (serverArgumentsIndex < serverArguments.size()) { + String argument = serverArguments.get(serverArgumentsIndex).getAsString(); + result = result.replace("{}", argument); + if (!result.equals(split)) { + serverArgumentsIndex++; + } + } + + message.append(result); + } + return message.toString(); + } + + + @Override + public String toString() { + return getMessageFormat(); + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/news/NewsType.java b/common/src/main/java/org/geysermc/floodgate/news/NewsType.java new file mode 100644 index 000000000..c2848535e --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/news/NewsType.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.floodgate.news; + +import com.google.gson.JsonObject; +import org.geysermc.floodgate.news.data.BuildSpecificData; +import org.geysermc.floodgate.news.data.CheckAfterData; +import org.geysermc.floodgate.news.data.ItemData; + +import java.util.function.Function; + +public enum NewsType { + BUILD_SPECIFIC(BuildSpecificData::read), + CHECK_AFTER(CheckAfterData::read); + + private static final NewsType[] VALUES = values(); + + private final Function readFunction; + + NewsType(Function readFunction) { + this.readFunction = readFunction; + } + + public static NewsType getByName(String newsType) { + for (NewsType type : VALUES) { + if (type.name().equalsIgnoreCase(newsType)) { + return type; + } + } + return null; + } + + public ItemData read(JsonObject data) { + return readFunction.apply(data); + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/news/data/BuildSpecificData.java b/common/src/main/java/org/geysermc/floodgate/news/data/BuildSpecificData.java new file mode 100644 index 000000000..d415f4200 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/news/data/BuildSpecificData.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.floodgate.news.data; + +import com.google.gson.JsonObject; + +public final class BuildSpecificData implements ItemData { + private String branch; + + private boolean allAffected; + private int affectedGreaterThan; + private int affectedLessThan; + + public static BuildSpecificData read(JsonObject data) { + BuildSpecificData updateData = new BuildSpecificData(); + updateData.branch = data.get("branch").getAsString(); + + JsonObject affectedBuilds = data.getAsJsonObject("affected_builds"); + if (affectedBuilds.has("all")) { + updateData.allAffected = affectedBuilds.get("all").getAsBoolean(); + } + if (!updateData.allAffected) { + updateData.affectedGreaterThan = affectedBuilds.get("gt").getAsInt(); + updateData.affectedLessThan = affectedBuilds.get("lt").getAsInt(); + } + return updateData; + } + + public boolean isAffected(String branch, int buildId) { + return this.branch.equals(branch) && + (allAffected || buildId > affectedGreaterThan && buildId < affectedLessThan); + } + + public String getBranch() { + return branch; + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/news/data/CheckAfterData.java b/common/src/main/java/org/geysermc/floodgate/news/data/CheckAfterData.java new file mode 100644 index 000000000..92d01689b --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/news/data/CheckAfterData.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.floodgate.news.data; + +import com.google.gson.JsonObject; + +public class CheckAfterData implements ItemData { + private long checkAfter; + + public static CheckAfterData read(JsonObject data) { + CheckAfterData checkAfterData = new CheckAfterData(); + checkAfterData.checkAfter = data.get("check_after").getAsLong(); + return checkAfterData; + } + + public long getCheckAfter() { + return checkAfter; + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/news/data/ItemData.java b/common/src/main/java/org/geysermc/floodgate/news/data/ItemData.java new file mode 100644 index 000000000..122ee775d --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/news/data/ItemData.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.floodgate.news.data; + +public interface ItemData { +} diff --git a/common/src/main/java/org/geysermc/floodgate/time/TimeSyncer.java b/common/src/main/java/org/geysermc/floodgate/time/TimeSyncer.java index 1e385b2c6..3fe089e06 100644 --- a/common/src/main/java/org/geysermc/floodgate/time/TimeSyncer.java +++ b/common/src/main/java/org/geysermc/floodgate/time/TimeSyncer.java @@ -25,18 +25,16 @@ package org.geysermc.floodgate.time; -import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public final class TimeSyncer { - private final ExecutorService executorService; + private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1); private long timeOffset = Long.MIN_VALUE; // value when it failed to get the offset public TimeSyncer(String timeServer) { - ScheduledExecutorService service = Executors.newScheduledThreadPool(1); - service.scheduleWithFixedDelay(() -> { + executorService.scheduleWithFixedDelay(() -> { // 5 tries to get the time offset, since UDP doesn't guaranty a response for (int i = 0; i < 5; i++) { long offset = SntpClientUtils.requestTimeOffset(timeServer, 3000); @@ -46,7 +44,6 @@ public final class TimeSyncer { } } }, 0, 30, TimeUnit.MINUTES); - executorService = service; } public void shutdown() { diff --git a/common/src/main/java/org/geysermc/floodgate/util/WebsocketEventType.java b/common/src/main/java/org/geysermc/floodgate/util/WebsocketEventType.java index 1025fcdba..f24dc8145 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/WebsocketEventType.java +++ b/common/src/main/java/org/geysermc/floodgate/util/WebsocketEventType.java @@ -26,16 +26,66 @@ package org.geysermc.floodgate.util; public enum WebsocketEventType { - SUBSCRIBER_CREATED, - SUBSCRIBERS_COUNT, - ADDED_TO_QUEUE, - SKIN_UPLOADED, - CREATOR_DISCONNECTED, - LOG_MESSAGE; + /** + * Sent once we successfully connected to the server + */ + SUBSCRIBER_CREATED(0), + /** + * Sent every time a subscriber got added or disconnected + */ + SUBSCRIBER_COUNT(1), + /** + * Sent once the creator disconnected. After this packet the server will automatically close the + * connection once the queue size (sent in {@link #ADDED_TO_QUEUE} and {@link #SKIN_UPLOADED} + * reaches 0. + */ + CREATOR_DISCONNECTED(4), - public static final WebsocketEventType[] VALUES = values(); + /** + * Sent every time a skin got added to the upload queue + */ + ADDED_TO_QUEUE(2), + /** + * Sent every time a skin got successfully uploaded + */ + SKIN_UPLOADED(3), + + /** + * Sent every time a news item was added + */ + NEWS_ADDED(6), + + /** + * Sent when the server wants you to know something. Currently used for violations that aren't + * bad enough to close the connection + */ + LOG_MESSAGE(5); + + private static final WebsocketEventType[] VALUES; + + static { + WebsocketEventType[] values = values(); + VALUES = new WebsocketEventType[values.length]; + for (WebsocketEventType value : values) { + VALUES[value.id] = value; + } + } + + /** + * The ID is based of the time it got added. However, to keep the enum organized as time goes on, + * it looks nicer to sort the events based of categories. + */ + private final int id; + + WebsocketEventType(int id) { + this.id = id; + } public static WebsocketEventType getById(int id) { return VALUES.length > id ? VALUES[id] : null; } + + public int getId() { + return id; + } } diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 52f3680e2..87a698be3 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -64,6 +64,7 @@ import org.geysermc.floodgate.crypto.AesCipher; import org.geysermc.floodgate.crypto.AesKeyProducer; import org.geysermc.floodgate.crypto.Base64Topping; import org.geysermc.floodgate.crypto.FloodgateCipher; +import org.geysermc.floodgate.news.NewsItemAction; import org.geysermc.floodgate.time.TimeSyncer; import org.jetbrains.annotations.Contract; @@ -111,6 +112,7 @@ public class GeyserConnector { private TimeSyncer timeSyncer; private FloodgateCipher cipher; private FloodgateSkinUploader skinUploader; + private final NewsHandler newsHandler; private boolean shuttingDown = false; @@ -213,6 +215,21 @@ public class GeyserConnector { } } + String branch = "unknown"; + int buildNumber = -1; + try { + Properties gitProperties = new Properties(); + gitProperties.load(FileUtils.getResource("git.properties")); + branch = gitProperties.getProperty("git.branch"); + String build = gitProperties.getProperty("git.build.number"); + if (build != null) { + buildNumber = Integer.parseInt(build); + } + } catch (Exception e) { + logger.error("Failed to read git.properties", e); + } + newsHandler = new NewsHandler(branch, buildNumber); + CooldownUtils.setDefaultShowCooldown(config.getShowCooldown()); DimensionUtils.changeBedrockNetherId(config.isAboveBedrockNetherBuilding()); // Apply End dimension ID workaround to Nether SkullBlockEntityTranslator.ALLOW_CUSTOM_SKULLS = config.isAllowCustomSkulls(); @@ -326,6 +343,8 @@ public class GeyserConnector { if (platformType == PlatformType.STANDALONE) { logger.warning(LanguageUtils.getLocaleStringLog("geyser.core.movement_warn")); } + + newsHandler.handleNews(null, NewsItemAction.ON_SERVER_STARTED); } public void shutdown() { @@ -371,6 +390,7 @@ public class GeyserConnector { generalThreadPool.shutdown(); bedrockServer.close(); timeSyncer.shutdown(); + newsHandler.shutdown(); players.clear(); defaultAuthType = null; this.getCommandManager().getCommands().clear(); diff --git a/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java b/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java index dd541864a..beed78e05 100644 --- a/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java +++ b/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java @@ -63,7 +63,7 @@ public final class FloodgateSkinUploader { public FloodgateSkinUploader(GeyserConnector connector) { this.logger = connector.getLogger(); - this.client = new WebSocketClient(Constants.SKIN_UPLOAD_URI) { + this.client = new WebSocketClient(Constants.GLOBAL_API_WS_URI) { @Override public void onOpen(ServerHandshake handshake) { setConnectionLostTimeout(11); @@ -99,7 +99,7 @@ public final class FloodgateSkinUploader { id = node.get("id").asInt(); verifyCode = node.get("verify_code").asText(); break; - case SUBSCRIBERS_COUNT: + case SUBSCRIBER_COUNT: subscribersCount = node.get("subscribers_count").asInt(); break; case SKIN_UPLOADED: @@ -145,6 +145,8 @@ public final class FloodgateSkinUploader { break; } break; + case NEWS_ADDED: + //todo } } catch (Exception e) { logger.error("Error while receiving a message", e); diff --git a/connector/src/main/java/org/geysermc/connector/utils/Constants.java b/connector/src/main/java/org/geysermc/connector/utils/Constants.java index 5b21d045c..a0cc9bf80 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/Constants.java +++ b/connector/src/main/java/org/geysermc/connector/utils/Constants.java @@ -27,18 +27,28 @@ package org.geysermc.connector.utils; import java.net.URI; import java.net.URISyntaxException; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; public final class Constants { - public static final URI SKIN_UPLOAD_URI; + public static final URI GLOBAL_API_WS_URI; public static final String NTP_SERVER = "time.cloudflare.com"; + public static final Set NEWS_PROJECT_LIST = Collections.unmodifiableSet( + new HashSet<>(Arrays.asList("geyser", "floodgate")) + ); + + public static final String NEWS_OVERVIEW_URL = "https://api.geysermc.org/v1/news"; + static { - URI skinUploadUri = null; + URI wsUri = null; try { - skinUploadUri = new URI("wss://api.geysermc.org/ws"); + wsUri = new URI("wss://api.geysermc.org/ws"); } catch (URISyntaxException e) { e.printStackTrace(); } - SKIN_UPLOAD_URI = skinUploadUri; + GLOBAL_API_WS_URI = wsUri; } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/NewsHandler.java b/connector/src/main/java/org/geysermc/connector/utils/NewsHandler.java new file mode 100644 index 000000000..aa8fe2efc --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/utils/NewsHandler.java @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.utils; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonSyntaxException; +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.GeyserLogger; +import org.geysermc.connector.common.ChatColor; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.floodgate.news.NewsItem; +import org.geysermc.floodgate.news.NewsItemAction; +import org.geysermc.floodgate.news.data.BuildSpecificData; +import org.geysermc.floodgate.news.data.CheckAfterData; + +import java.util.*; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +public class NewsHandler { + private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1); + private final GeyserLogger logger = GeyserConnector.getInstance().getLogger(); + private final Gson gson = new Gson(); + + private final Map activeNewsItems = new HashMap<>(); + private final String branch; + private final int build; + + private boolean geyserStarted; + + public NewsHandler(String branch, int build) { + this.branch = branch; + this.build = build; + + executorService.scheduleWithFixedDelay(this::checkNews, 0, 30, TimeUnit.MINUTES); + } + + private void schedule(long delayMs) { + executorService.schedule(this::checkNews, delayMs, TimeUnit.MILLISECONDS); + } + + private void checkNews() { + try { + String body = WebUtils.getBody(Constants.NEWS_OVERVIEW_URL); + JsonArray array = gson.fromJson(body, JsonArray.class); + + try { + for (JsonElement newsItemElement : array) { + NewsItem newsItem = NewsItem.readItem(newsItemElement.getAsJsonObject()); + if (newsItem != null) { + addNews(newsItem); + } + } + } catch (Exception e) { + if (logger.isDebug()) { + logger.error("Error while reading news item", e); + } + } + } catch (JsonSyntaxException ignored) {} + } + + public void setGeyserStarted() { + geyserStarted = true; + } + + public void handleNews(GeyserSession session, NewsItemAction action) { + for (NewsItem news : getActiveNews(action)) { + handleNewsItem(session, news, action); + } + } + + private void handleNewsItem(GeyserSession session, NewsItem news, NewsItemAction action) { + switch (action) { + case ON_SERVER_STARTED: + if (!geyserStarted) { + return; + } + case BROADCAST_TO_CONSOLE: + logger.info(news.getMessage()); + break; + case ON_OPERATOR_JOIN: + //todo doesn't work, it's called before we know the op level. +// if (session != null && session.getOpPermissionLevel() >= 2) { +// session.sendMessage(ChatColor.GREEN + news.getMessage()); +// } + break; + case BROADCAST_TO_OPERATORS: + for (GeyserSession player : GeyserConnector.getInstance().getPlayers()) { + if (player.getOpPermissionLevel() >= 2) { + session.sendMessage(ChatColor.GREEN + news.getMessage()); + } + } + break; + } + } + + public Collection getActiveNews() { + return activeNewsItems.values(); + } + + public Collection getActiveNews(NewsItemAction action) { + List news = new ArrayList<>(); + for (NewsItem item : getActiveNews()) { + if (item.getActions().contains(action)) { + news.add(item); + } + } + return news; + } + + public void addNews(NewsItem item) { + if (activeNewsItems.containsKey(item.getId())) { + if (!item.isActive()) { + activeNewsItems.remove(item.getId()); + } + return; + } + + if (!Constants.NEWS_PROJECT_LIST.contains(item.getProject())) { + return; + } + + switch (item.getType()) { + case BUILD_SPECIFIC: + if (!item.getDataAs(BuildSpecificData.class).isAffected(branch, build)) { + return; + } + break; + case CHECK_AFTER: + long checkAfter = item.getDataAs(CheckAfterData.class).getCheckAfter(); + long delayMs = System.currentTimeMillis() - checkAfter; + schedule(delayMs > 0 ? delayMs : 0); + break; + } + + activeNewsItems.put(item.getId(), item); + activateNews(item); + } + + private void activateNews(NewsItem item) { + for (NewsItemAction action : item.getActions()) { + handleNewsItem(null, item, action); + } + } + + public void shutdown() { + executorService.shutdown(); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/utils/WebUtils.java b/connector/src/main/java/org/geysermc/connector/utils/WebUtils.java index ba008da41..cf9f0e1fb 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/WebUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/WebUtils.java @@ -45,9 +45,8 @@ public class WebUtils { * @return Body contents or error message if the request fails */ public static String getBody(String reqURL) { - URL url = null; try { - url = new URL(reqURL); + URL url = new URL(reqURL); HttpURLConnection con = (HttpURLConnection) url.openConnection(); con.setRequestMethod("GET"); con.setRequestProperty("User-Agent", "Geyser-" + GeyserConnector.getInstance().getPlatformType().toString() + "/" + GeyserConnector.VERSION); // Otherwise Java 8 fails on checking updates From 01492647fcfb5b24000abc4bc88470b6f03a2e9b Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sun, 6 Jun 2021 00:53:58 +0200 Subject: [PATCH 073/107] Some small changes --- .../geysermc/floodgate/util/Base64Utils.java | 2 +- .../floodgate/util/FloodgateConfigHolder.java | 2 +- .../geysermc/floodgate/util/LinkedPlayer.java | 2 +- connector/pom.xml | 4 +++ .../geysermc/connector/GeyserConnector.java | 4 ++- .../connector/command/CommandManager.java | 2 +- .../network/session/GeyserSession.java | 6 ++-- .../session/auth/BedrockClientData.java | 29 ------------------- .../session/cache/AdvancementsCache.java | 3 +- .../network/session/cache/FormCache.java | 17 ++++++----- .../BedrockNetworkStackLatencyTranslator.java | 1 - .../java/JavaAdvancementsTabTranslator.java | 7 +++-- .../java/JavaStatisticsTranslator.java | 1 + .../connector/utils/LoginEncryptionUtils.java | 3 +- .../connector/utils/StatisticsUtils.java | 4 +-- 15 files changed, 33 insertions(+), 54 deletions(-) diff --git a/common/src/main/java/org/geysermc/floodgate/util/Base64Utils.java b/common/src/main/java/org/geysermc/floodgate/util/Base64Utils.java index 4c527b948..326fa2590 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/Base64Utils.java +++ b/common/src/main/java/org/geysermc/floodgate/util/Base64Utils.java @@ -25,7 +25,7 @@ package org.geysermc.floodgate.util; -public class Base64Utils { +public final class Base64Utils { public static int getEncodedLength(int length) { if (length <= 0) { return -1; diff --git a/common/src/main/java/org/geysermc/floodgate/util/FloodgateConfigHolder.java b/common/src/main/java/org/geysermc/floodgate/util/FloodgateConfigHolder.java index 386213ada..d33840bb4 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/FloodgateConfigHolder.java +++ b/common/src/main/java/org/geysermc/floodgate/util/FloodgateConfigHolder.java @@ -28,7 +28,7 @@ package org.geysermc.floodgate.util; import lombok.Getter; import lombok.Setter; -public class FloodgateConfigHolder { +public final class FloodgateConfigHolder { @Getter @Setter private static Object config; diff --git a/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java b/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java index 7d67a44a0..f91bfafbc 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java +++ b/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java @@ -48,7 +48,7 @@ public final class LinkedPlayer implements Cloneable { */ private final UUID bedrockId; /** - * If the LinkedPlayer is send from a different platform. For example the LinkedPlayer is from + * If the LinkedPlayer is sent from a different platform. For example the LinkedPlayer is from * Bungee but the data has been sent to the Bukkit server. */ private boolean fromDifferentPlatform = false; diff --git a/connector/pom.xml b/connector/pom.xml index 1ed01a0c4..b7bfc9d3a 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -39,6 +39,7 @@ org.java-websocket Java-WebSocket 1.5.1 + compile com.github.CloudburstMC.Protocol @@ -214,11 +215,13 @@ org.reflections reflections 0.9.11 + compile org.dom4j dom4j 2.1.3 + compile net.kyori @@ -254,6 +257,7 @@ com.github.GeyserMC MCAuthLib 0e48a094f2 + compile diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 87a698be3..8dfa12eb4 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -109,7 +109,7 @@ public class GeyserConnector { @Setter private AuthType defaultAuthType; - private TimeSyncer timeSyncer; + private final TimeSyncer timeSyncer; private FloodgateCipher cipher; private FloodgateSkinUploader skinUploader; private final NewsHandler newsHandler; @@ -202,6 +202,7 @@ public class GeyserConnector { defaultAuthType = AuthType.getByName(config.getRemote().getAuthType()); + TimeSyncer timeSyncer = null; if (defaultAuthType == AuthType.FLOODGATE) { timeSyncer = new TimeSyncer(Constants.NTP_SERVER); try { @@ -214,6 +215,7 @@ public class GeyserConnector { logger.severe(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.bad_key"), exception); } } + this.timeSyncer = timeSyncer; String branch = "unknown"; int buildNumber = -1; 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 71eb2c742..f62fd8539 100644 --- a/connector/src/main/java/org/geysermc/connector/command/CommandManager.java +++ b/connector/src/main/java/org/geysermc/connector/command/CommandManager.java @@ -53,7 +53,7 @@ public abstract class CommandManager { registerCommand(new VersionCommand(connector, "version", "geyser.commands.version.desc", "geyser.command.version")); registerCommand(new SettingsCommand(connector, "settings", "geyser.commands.settings.desc", "geyser.command.settings")); registerCommand(new StatisticsCommand(connector, "statistics", "geyser.commands.statistics.desc", "geyser.command.statistics")); - registerCommand(new AdvancementsCommand( "advancements", "geyser.commands.advancements.desc", "geyser.command.advancements")); + registerCommand(new AdvancementsCommand("advancements", "geyser.commands.advancements.desc", "geyser.command.advancements")); } public void registerCommand(GeyserCommand command) { 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 ead9d1834..f901fe9a1 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 @@ -140,10 +140,10 @@ public class GeyserSession implements CommandSender { private ChunkCache chunkCache; private EntityCache entityCache; private EntityEffectCache effectCache; + private final FormCache formCache; private final PreferencesCache preferencesCache; private final TagCache tagCache; private WorldCache worldCache; - private FormCache formCache; private final Int2ObjectMap teleportMap = new Int2ObjectOpenHashMap<>(); private final PlayerInventory playerInventory; @@ -445,12 +445,13 @@ public class GeyserSession implements CommandSender { this.chunkCache = new ChunkCache(this); this.entityCache = new EntityCache(this); this.effectCache = new EntityEffectCache(); + this.formCache = new FormCache(this); this.preferencesCache = new PreferencesCache(this); this.tagCache = new TagCache(); this.worldCache = new WorldCache(this); - this.formCache = new FormCache(this); this.collisionManager = new CollisionManager(this); + this.playerEntity = new SessionPlayerEntity(this); collisionManager.updatePlayerBoundingBox(this.playerEntity.getPosition()); @@ -851,7 +852,6 @@ public class GeyserSession implements CommandSender { this.entityCache = null; this.effectCache = null; this.worldCache = null; - this.formCache = null; closed = true; } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java b/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java index d87590e5f..fc4d1164a 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java @@ -25,26 +25,18 @@ package org.geysermc.connector.network.session.auth; -import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.JsonNode; -import com.google.common.base.Charsets; import lombok.Getter; -import org.geysermc.connector.skin.SkinProvider; import org.geysermc.floodgate.util.DeviceOs; import org.geysermc.floodgate.util.InputMode; import org.geysermc.floodgate.util.UiProfile; -import java.util.Base64; import java.util.UUID; @JsonIgnoreProperties(ignoreUnknown = true) @Getter public final class BedrockClientData { - @JsonIgnore - private JsonNode jsonData; - @JsonProperty(value = "GameVersion") private String gameVersion; @JsonProperty(value = "ServerAddress") @@ -115,27 +107,6 @@ public final class BedrockClientData { @JsonProperty(value = "PlayFabId") private String playFabId; - public void setJsonData(JsonNode data) { - if (this.jsonData == null && data != null) { - this.jsonData = data; - } - } - - public boolean isAlex() { - try { - byte[] bytes = Base64.getDecoder().decode(geometryName.getBytes(Charsets.UTF_8)); - String geometryName = - SkinProvider.OBJECT_MAPPER - .readTree(bytes) - .get("geometry").get("default") - .asText(); - return "geometry.humanoid.customSlim".equals(geometryName); - } catch (Exception exception) { - exception.printStackTrace(); - return false; - } - } - public DeviceOs getDeviceOs() { return deviceOs != null ? deviceOs : DeviceOs.UNKNOWN; } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/AdvancementsCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/AdvancementsCache.java index d20eb11dd..98ec5b262 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/AdvancementsCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/AdvancementsCache.java @@ -29,7 +29,6 @@ import com.github.steveice10.mc.protocol.data.game.advancement.Advancement; import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientAdvancementTabPacket; import lombok.Getter; import lombok.Setter; -import lombok.experimental.Accessors; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.chat.MessageTranslator; import org.geysermc.connector.utils.GeyserAdvancement; @@ -58,7 +57,7 @@ public class AdvancementsCache { /** * Stores player's chosen advancement's ID and title for use in form creators. */ - @Setter @Accessors(chain = true) + @Setter private String currentAdvancementCategoryId = null; private final GeyserSession session; diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java index cdd8a01ac..1cdcf228b 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java @@ -33,6 +33,7 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import lombok.RequiredArgsConstructor; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.cumulus.Form; +import org.geysermc.cumulus.SimpleForm; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -58,13 +59,15 @@ public class FormCache { formRequestPacket.setFormData(form.getJsonData()); session.sendUpstreamPacket(formRequestPacket); - // Hack to fix the url image loading bug - NetworkStackLatencyPacket latencyPacket = new NetworkStackLatencyPacket(); - latencyPacket.setFromServer(true); - latencyPacket.setTimestamp(-System.currentTimeMillis()); - session.getConnector().getGeneralThreadPool().schedule( - () -> session.sendUpstreamPacket(latencyPacket), - 500, TimeUnit.MILLISECONDS); + // Hack to fix the (url) image loading bug + if (form instanceof SimpleForm) { + NetworkStackLatencyPacket latencyPacket = new NetworkStackLatencyPacket(); + latencyPacket.setFromServer(true); + latencyPacket.setTimestamp(-System.currentTimeMillis()); + session.getConnector().getGeneralThreadPool().schedule( + () -> session.sendUpstreamPacket(latencyPacket), + 500, TimeUnit.MILLISECONDS); + } return windowId; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockNetworkStackLatencyTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockNetworkStackLatencyTranslator.java index 577469fdd..208e7c75d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockNetworkStackLatencyTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockNetworkStackLatencyTranslator.java @@ -52,7 +52,6 @@ public class BedrockNetworkStackLatencyTranslator extends PacketTranslator { @Override public void translate(ServerAdvancementTabPacket packet, GeyserSession session) { - session.getAdvancementsCache() - .setCurrentAdvancementCategoryId(packet.getTabId()) - .buildAndShowListForm(); + AdvancementsCache advancementsCache = session.getAdvancementsCache(); + advancementsCache.setCurrentAdvancementCategoryId(packet.getTabId()); + advancementsCache.buildAndShowListForm(); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaStatisticsTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaStatisticsTranslator.java index 5d3164c0d..247808041 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaStatisticsTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaStatisticsTranslator.java @@ -33,6 +33,7 @@ import org.geysermc.connector.utils.StatisticsUtils; @Translator(packet = ServerStatisticsPacket.class) public class JavaStatisticsTranslator extends PacketTranslator { + @Override public void translate(ServerStatisticsPacket packet, GeyserSession session) { session.updateStatistics(packet.getStatistics()); 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 ce9f23272..d3d5fa67d 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java @@ -134,7 +134,6 @@ public class LoginEncryptionUtils { JsonNode clientDataJson = JSON_MAPPER.readTree(clientJwt.getPayload().toBytes()); BedrockClientData data = JSON_MAPPER.convertValue(clientDataJson, BedrockClientData.class); - data.setJsonData(clientDataJson); session.setClientData(data); if (EncryptionUtils.canUseEncryption()) { @@ -243,7 +242,7 @@ public class LoginEncryptionUtils { } /** - * Promts the user between either OAuth code login or manual password authentication + * Prompts the user between either OAuth code login or manual password authentication */ public static void buildAndShowMicrosoftAuthenticationWindow(GeyserSession session) { session.sendForm( diff --git a/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java b/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java index f2181d871..e59807d75 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java @@ -180,8 +180,8 @@ public class StatisticsUtils { session.sendForm( builder.content(content.toString()) .button("gui.back", FormImage.Type.PATH, "textures/gui/newgui/undo") - .responseHandler((form1, responseData1) -> { - SimpleFormResponse response1 = form.parseResponse(responseData1); + .responseHandler((form1, subFormResponseData) -> { + SimpleFormResponse response1 = form.parseResponse(subFormResponseData); if (response1.isCorrect()) { buildAndSendStatisticsMenu(session); } From ab8ce77cf0de8df4ee069078db9b1ac3074ba6a3 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sun, 6 Jun 2021 01:05:22 +0200 Subject: [PATCH 074/107] Some small changes --- .../src/main/java/org/geysermc/floodgate/util/UiProfile.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/org/geysermc/floodgate/util/UiProfile.java b/common/src/main/java/org/geysermc/floodgate/util/UiProfile.java index 58d615eaa..af1121f3c 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/UiProfile.java +++ b/common/src/main/java/org/geysermc/floodgate/util/UiProfile.java @@ -27,7 +27,8 @@ package org.geysermc.floodgate.util; public enum UiProfile { - CLASSIC, POCKET; + CLASSIC, + POCKET; private static final UiProfile[] VALUES = values(); From 817d1587af00654f726b2369ff9aca42bc318c0b Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 5 Jun 2021 19:29:23 -0400 Subject: [PATCH 075/107] Prevent trader llama spawn egg from being used in creative --- .../connector/network/translators/item/ItemRegistry.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 5d5b5f569..2958e6f00 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 @@ -530,7 +530,7 @@ public class ItemRegistry { Set javaOnlyItems = new ObjectOpenHashSet<>(); Collections.addAll(javaOnlyItems, "minecraft:spectral_arrow", "minecraft:debug_stick", - "minecraft:knowledge_book", "minecraft:tipped_arrow"); + "minecraft:knowledge_book", "minecraft:tipped_arrow", "minecraft:trader_llama_spawn_egg"); if (!usingFurnaceMinecart) { javaOnlyItems.add("minecraft:furnace_minecart"); } From c29a1ab3afcf273620e7b3e5d1b7f6465edafe52 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 5 Jun 2021 19:33:22 -0400 Subject: [PATCH 076/107] 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 bcb49d0e4..ea0056203 160000 --- a/connector/src/main/resources/mappings +++ b/connector/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit bcb49d0e463dcac2ad78e3153d3281915fbbbcb2 +Subproject commit ea0056203d62db84a61bee285e0d85d22cb21bff From fc4f2b7890b7635163d524542a06f248db02f4d3 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 6 Jun 2021 09:35:56 -0400 Subject: [PATCH 077/107] Remove usage of translation strings for code-related errors These should never ever be seen by a normal user, and if so, developers need to see the proper error message --- .../network/translators/PacketTranslatorRegistry.java | 4 ++-- .../network/translators/item/ItemTranslator.java | 7 ++++--- .../network/translators/world/block/BlockTranslator.java | 8 +++++++- .../world/block/entity/BlockEntityTranslator.java | 5 ++--- connector/src/main/resources/languages | 2 +- 5 files changed, 16 insertions(+), 10 deletions(-) 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 eb26dbff9..2469f65da 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 @@ -68,10 +68,10 @@ public class PacketTranslatorRegistry { BEDROCK_TRANSLATOR.translators.put(targetPacket, translator); } else { - GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.network.translator.invalid_target", clazz.getCanonicalName())); + GeyserConnector.getInstance().getLogger().error("Class " + clazz.getCanonicalName() + " is annotated as a translator but has an invalid target packet."); } } catch (InstantiationException | IllegalAccessException e) { - GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.network.translator.failed", clazz.getCanonicalName())); + GeyserConnector.getInstance().getLogger().error("Could not instantiate annotated translator " + clazz.getCanonicalName()); } } 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 78bfd07ce..afb520881 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 @@ -39,7 +39,6 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.ItemRemapper; import org.geysermc.connector.network.translators.chat.MessageTranslator; import org.geysermc.connector.utils.FileUtils; -import org.geysermc.connector.utils.LanguageUtils; import org.geysermc.connector.utils.LocaleUtils; import org.reflections.Reflections; @@ -78,13 +77,15 @@ public abstract class ItemTranslator { for (ItemEntry item : appliedItems) { ItemTranslator registered = ITEM_STACK_TRANSLATORS.get(item.getJavaId()); if (registered != null) { - GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.network.translator.item.already_registered", clazz.getCanonicalName(), registered.getClass().getCanonicalName(), item.getJavaIdentifier())); + 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()); continue; } ITEM_STACK_TRANSLATORS.put(item.getJavaId(), itemStackTranslator); } } catch (InstantiationException | IllegalAccessException e) { - GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.network.translator.item.failed", clazz.getCanonicalName())); + GeyserConnector.getInstance().getLogger().error("Could not instantiate annotated item translator " + clazz.getCanonicalName()); } } 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 d86a5edd2..43bd9d934 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 @@ -151,6 +151,13 @@ public abstract class BlockTranslator { builder.pickItem(pickItemNode.textValue()); } + boolean waterlogged = entry.getKey().contains("waterlogged=true") + || javaId.contains("minecraft:bubble_column") || javaId.contains("minecraft:kelp") || javaId.contains("seagrass"); + + if (waterlogged) { + WATERLOGGED.add(javaRuntimeId); + } + JAVA_ID_BLOCK_MAP.put(javaId, javaRuntimeId); BlockStateValues.storeBlockStateValues(entry.getKey(), javaRuntimeId, entry.getValue()); @@ -286,7 +293,6 @@ public abstract class BlockTranslator { if (waterlogged) { bedrockToJavaBlockMap.putIfAbsent(bedrockRuntimeId | 1 << 31, javaRuntimeId); - WATERLOGGED.add(javaRuntimeId); } else { bedrockToJavaBlockMap.putIfAbsent(bedrockRuntimeId, javaRuntimeId); } 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 4d277bc9d..983a3d06d 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 @@ -35,7 +35,6 @@ import it.unimi.dsi.fastutil.objects.ObjectArrayList; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.utils.BlockEntityUtils; import org.geysermc.connector.utils.FileUtils; -import org.geysermc.connector.utils.LanguageUtils; import org.reflections.Reflections; import java.util.HashMap; @@ -80,7 +79,7 @@ public abstract class BlockEntityTranslator { try { BLOCK_ENTITY_TRANSLATORS.put(clazz.getAnnotation(BlockEntity.class).name(), (BlockEntityTranslator) clazz.newInstance()); } catch (InstantiationException | IllegalAccessException e) { - GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.network.translator.block_entity.failed", clazz.getCanonicalName())); + GeyserConnector.getInstance().getLogger().error("Could not instantiate annotated block entity" + clazz.getCanonicalName()); } } for (Class clazz : ref.getSubTypesOf(BedrockOnlyBlockEntity.class)) { @@ -90,7 +89,7 @@ public abstract class BlockEntityTranslator { BedrockOnlyBlockEntity bedrockOnlyBlockEntity = (BedrockOnlyBlockEntity) clazz.newInstance(); BEDROCK_ONLY_BLOCK_ENTITIES.add(bedrockOnlyBlockEntity); } catch (InstantiationException | IllegalAccessException e) { - GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.network.translator.block_state.failed", clazz.getCanonicalName())); + GeyserConnector.getInstance().getLogger().error("Could not instantiate annotated block state " + clazz.getCanonicalName()); } } } diff --git a/connector/src/main/resources/languages b/connector/src/main/resources/languages index e1e8fd6c2..bf4bf0554 160000 --- a/connector/src/main/resources/languages +++ b/connector/src/main/resources/languages @@ -1 +1 @@ -Subproject commit e1e8fd6c2b8abf366e60085c23a55a2c943806ae +Subproject commit bf4bf0554bd9705f33fe4ccf4aab0ec0075350d4 From 0162257d306699b7dec4eae6d2bdc87cc14791e9 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 6 Jun 2021 09:55:38 -0400 Subject: [PATCH 078/107] Glowing text on signs is Bedrock 1.17.10. :( --- .../world/block/entity/SignBlockEntityTranslator.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 61ca4fa9c..ad6c32d4f 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 @@ -96,6 +96,7 @@ public class SignBlockEntityTranslator extends BlockEntityTranslator { @Override public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { + //TODO Bedrock 1.17.10 glow text StringBuilder signText = new StringBuilder(); for (int i = 0; i < 4; i++) { int currentLine = i + 1; @@ -132,7 +133,7 @@ public class SignBlockEntityTranslator extends BlockEntityTranslator { signText.append(getBedrockSignColor(color.getValue().toString())); } - signText.append(finalSignLine.toString()); + signText.append(finalSignLine); signText.append("\n"); } From fac1dc8871d58bae83f0f659efac7004bc90f3db Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 6 Jun 2021 09:59:37 -0400 Subject: [PATCH 079/107] Update README supported versions --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3af1b1e92..7db62fbb2 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have joined us here! -### Currently supporting Minecraft Bedrock v1.17.0.58 and Minecraft Java 1.17-pre3. +### Currently supporting Minecraft Bedrock v1.17.0.58 and Minecraft Java 1.17-rc1. ## Setting Up Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set up Geyser. From 2f0677b866dd13d453aa032d86e87f1ae8d96e74 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 6 Jun 2021 10:00:08 -0400 Subject: [PATCH 080/107] We no longer need to worry about deploying the Floodgate 2.0 branch --- Jenkinsfile | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index d76a6f737..09e88e86e 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -22,10 +22,7 @@ pipeline { stage ('Deploy') { when { - anyOf { - branch "master" - branch "floodgate-2.0" - } + branch "master" } steps { From aec27f8481eed8aaf87c5ce3cd302ea401d31573 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 6 Jun 2021 10:12:49 -0400 Subject: [PATCH 081/107] Remove usage of Jackson date and time dependency --- connector/pom.xml | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/connector/pom.xml b/connector/pom.xml index 6701225ae..471ce3415 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -11,9 +11,10 @@ connector - 4.1.59.Final - 8.5.2 4.7.0 + 8.5.2 + 2.10.2 + 4.1.59.Final @@ -23,16 +24,29 @@ 1.3.0-SNAPSHOT compile + - com.fasterxml.jackson.datatype - jackson-datatype-jsr310 - 2.10.2 + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + compile + + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} + compile + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} compile com.fasterxml.jackson.dataformat jackson-dataformat-yaml - 2.10.2 + ${jackson.version} compile From b2ebfc68033b1713f4e45e667c921dbe87e734e0 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 6 Jun 2021 10:19:24 -0400 Subject: [PATCH 082/107] Fix reload command (fixes #2255) --- .../main/java/org/geysermc/connector/GeyserConnector.java | 4 +++- .../geysermc/connector/command/defaults/ReloadCommand.java | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 34dd0b849..fc3aaaeb9 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -391,7 +391,9 @@ public class GeyserConnector { generalThreadPool.shutdown(); bedrockServer.close(); - timeSyncer.shutdown(); + if (timeSyncer != null) { + timeSyncer.shutdown(); + } newsHandler.shutdown(); players.clear(); defaultAuthType = null; 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 2f1c7dc9b..d3f3d2f3f 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 @@ -32,6 +32,8 @@ import org.geysermc.connector.command.GeyserCommand; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.utils.LanguageUtils; +import java.util.ArrayList; + public class ReloadCommand extends GeyserCommand { private final GeyserConnector connector; @@ -51,8 +53,8 @@ public class ReloadCommand extends GeyserCommand { sender.sendMessage(message); - for (GeyserSession otherSession : connector.getPlayers()) { - otherSession.disconnect(LanguageUtils.getPlayerLocaleString("geyser.commands.reload.kick", session.getLocale())); + for (GeyserSession otherSession : new ArrayList<>(connector.getPlayers())) { + otherSession.disconnect(LanguageUtils.getPlayerLocaleString("geyser.commands.reload.kick", otherSession.getLocale())); } connector.reload(); } From 632f84fcf837c6619201e32554e111e609b19aff Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Sun, 6 Jun 2021 10:59:47 -0400 Subject: [PATCH 083/107] Merge pull request #2095 * Add floodgate git properties to dump * Merge remote-tracking branch 'upstream/floodgate-2.0' into floodgate-2.0 * Modify the dump to not break anything * name changes to floodgate info in dump * small adjustment to DumpInfo.java * Merge remote-tracking branch 'upstream/floodgate-2.0' into floodgate-2.0 * remove duplicate import statements * Update mappings --- .../util/FloodgateGitPropertiesHolder.java | 36 +++++++++++++++++++ .../org/geysermc/connector/dump/DumpInfo.java | 33 +++++++++++------ connector/src/main/resources/mappings | 2 +- 3 files changed, 59 insertions(+), 12 deletions(-) create mode 100644 common/src/main/java/org/geysermc/floodgate/util/FloodgateGitPropertiesHolder.java diff --git a/common/src/main/java/org/geysermc/floodgate/util/FloodgateGitPropertiesHolder.java b/common/src/main/java/org/geysermc/floodgate/util/FloodgateGitPropertiesHolder.java new file mode 100644 index 000000000..5f157aceb --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/util/FloodgateGitPropertiesHolder.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.floodgate.util; + +import lombok.Getter; +import lombok.Setter; +import java.util.Properties; + +public class FloodgateGitPropertiesHolder { + @Getter + @Setter + private static Properties gitProperties; +} diff --git a/connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java b/connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java index f381bd510..f83598469 100644 --- a/connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java +++ b/connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java @@ -43,6 +43,7 @@ import org.geysermc.connector.utils.DockerCheck; import org.geysermc.connector.utils.FileUtils; import org.geysermc.floodgate.util.DeviceOs; import org.geysermc.floodgate.util.FloodgateConfigHolder; +import org.geysermc.floodgate.util.FloodgateGitPropertiesHolder; import java.io.File; import java.io.IOException; @@ -60,9 +61,9 @@ public class DumpInfo { private final DumpInfo.VersionInfo versionInfo; private Properties gitInfo; private final GeyserConfiguration config; - private final Object floodgateConfig; - private final HashInfo hashInfo; + private final Floodgate floodgate; private final Object2IntMap userPlatforms; + private final HashInfo hashInfo; private final RamInfo ramInfo; private final BootstrapDumpInfo bootstrapInfo; @@ -76,7 +77,7 @@ public class DumpInfo { } this.config = GeyserConnector.getInstance().getConfig(); - this.floodgateConfig = FloodgateConfigHolder.getConfig(); + this.floodgate = new Floodgate(); String md5Hash = "unknown"; String sha256Hash = "unknown"; @@ -95,7 +96,6 @@ public class DumpInfo { e.printStackTrace(); } } - this.hashInfo = new HashInfo(md5Hash, sha256Hash); this.ramInfo = new DumpInfo.RamInfo(); @@ -135,13 +135,6 @@ public class DumpInfo { } } - @AllArgsConstructor - @Getter - public static class HashInfo { - private final String md5Hash; - private final String sha256Hash; - } - @Getter public static class NetworkInfo { private final boolean dockerCheck; @@ -185,6 +178,24 @@ public class DumpInfo { } } + @Getter + public static class Floodgate { + private final Properties gitInfo; + private final Object config; + + Floodgate() { + this.gitInfo = FloodgateGitPropertiesHolder.getGitProperties(); + this.config = FloodgateConfigHolder.getConfig(); + } + } + + @AllArgsConstructor + @Getter + public static class HashInfo { + private final String md5Hash; + private final String sha256Hash; + } + @Getter public static class RamInfo { private final long free; diff --git a/connector/src/main/resources/mappings b/connector/src/main/resources/mappings index 53e13b7a0..c846b8200 160000 --- a/connector/src/main/resources/mappings +++ b/connector/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit 53e13b7a0d2ea14df71ed0c9582d29a9b4fb4453 +Subproject commit c846b8200eb8ebb37207666f7eddb83f2b636c37 From ef0503ede05f50cd8732aa794b7d6fbb2000c5f7 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 6 Jun 2021 11:36:11 -0400 Subject: [PATCH 084/107] Properly implement freezing ticks --- .../main/java/org/geysermc/connector/entity/Entity.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 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 505278653..90d0d36b2 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/Entity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/Entity.java @@ -305,9 +305,11 @@ public class Entity { metadata.getFlags().setFlag(EntityFlag.SWIMMING, pose.equals(Pose.SWIMMING)); setDimensions(pose); break; - case 7: - //TODO check - metadata.put(EntityData.FREEZING_EFFECT_STRENGTH, entityMetadata.getValue()); + case 7: // Freezing ticks + // The value that Java edition gives us is in ticks, but Bedrock uses a float percentage of the strength 0.0 -> 1.0 + // The Java client caps its freezing tick percentage at 140 + int freezingTicks = Math.min((int) entityMetadata.getValue(), 140); + metadata.put(EntityData.FREEZING_EFFECT_STRENGTH, (freezingTicks / (float) 140)); break; } } From 01ca2d3dcb70a8d701165691bc18a87afa181267 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 6 Jun 2021 11:38:54 -0400 Subject: [PATCH 085/107] Pufferfish puff is indeed fixed --- .../connector/entity/living/animal/PufferFishEntity.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/PufferFishEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/PufferFishEntity.java index 66f81e42f..0ddd581f8 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/PufferFishEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/PufferFishEntity.java @@ -41,8 +41,6 @@ public class PufferFishEntity extends AbstractFishEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { if (entityMetadata.getId() == 17) { - // Transfers correctly but doesn't apply on the client - //TODO check - probably because we didn't set PUFFERFISH_SIZE as a byte int puffsize = (int) entityMetadata.getValue(); metadata.put(EntityData.PUFFERFISH_SIZE, (byte) puffsize); metadata.put(EntityData.VARIANT, puffsize); From 207af5dffbf46d688ac960651e8390704afa8397 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 6 Jun 2021 11:58:03 -0400 Subject: [PATCH 086/107] Listen to PlayerPositionRotationTranslator#dismountVehicle --- .../JavaEntitySetPassengersTranslator.java | 149 +---------------- .../JavaPlayerPositionRotationTranslator.java | 21 +++ .../geysermc/connector/utils/EntityUtils.java | 152 +++++++++++++++++- 3 files changed, 175 insertions(+), 147 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntitySetPassengersTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntitySetPassengersTranslator.java index 23a7bcc4b..66d3e3880 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 @@ -26,19 +26,16 @@ package org.geysermc.connector.network.translators.java.entity; import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntitySetPassengersPacket; -import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.data.entity.EntityLinkData; import com.nukkitx.protocol.bedrock.packet.SetEntityLinkPacket; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.entity.living.ArmorStandEntity; -import org.geysermc.connector.entity.living.animal.AnimalEntity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; +import org.geysermc.connector.utils.EntityUtils; import java.util.Arrays; @@ -117,9 +114,9 @@ public class JavaEntitySetPassengersTranslator extends PacketTranslator 1)); + EntityUtils.updateMountOffset(passenger, entity, session, false, false, (packet.getPassengerIds().length > 1)); } else { - this.updateOffset(passenger, entity, session, (packet.getPassengerIds()[0] == passengerId), true, (packet.getPassengerIds().length > 1)); + EntityUtils.updateMountOffset(passenger, entity, session, (packet.getPassengerIds()[0] == passengerId), true, (packet.getPassengerIds().length > 1)); } // Force an update to the passenger metadata @@ -137,144 +134,4 @@ public class JavaEntitySetPassengersTranslator extends PacketTranslator 1); + } + // If coordinates are relative, then add to the existing coordinate double newX = packet.getX() + (packet.getRelative().contains(PositionElement.X) ? entity.getPosition().getX() : 0); diff --git a/connector/src/main/java/org/geysermc/connector/utils/EntityUtils.java b/connector/src/main/java/org/geysermc/connector/utils/EntityUtils.java index eb712b135..b5b0d24ca 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/EntityUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/EntityUtils.java @@ -26,9 +26,16 @@ package org.geysermc.connector.utils; import com.github.steveice10.mc.protocol.data.game.entity.Effect; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.connector.entity.Entity; +import org.geysermc.connector.entity.living.ArmorStandEntity; +import org.geysermc.connector.entity.living.animal.AnimalEntity; import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; -public class EntityUtils { +public final class EntityUtils { /** * Convert Java edition effect IDs to Bedrock edition @@ -72,4 +79,147 @@ public class EntityUtils { return null; } } + + private static float getMountedHeightOffset(Entity mount) { + float height = mount.getMetadata().getFloat(EntityData.BOUNDING_BOX_HEIGHT); + float mountedHeightOffset = height * 0.75f; + switch (mount.getEntityType()) { + case CHICKEN: + case SPIDER: + mountedHeightOffset = height * 0.5f; + break; + case DONKEY: + case MULE: + mountedHeightOffset -= 0.25f; + break; + case LLAMA: + mountedHeightOffset = height * 0.67f; + break; + case MINECART: + case MINECART_HOPPER: + case MINECART_TNT: + case MINECART_CHEST: + case MINECART_FURNACE: + case MINECART_SPAWNER: + case MINECART_COMMAND_BLOCK: + mountedHeightOffset = 0; + break; + case BOAT: + mountedHeightOffset = -0.1f; + break; + case HOGLIN: + case ZOGLIN: + boolean isBaby = mount.getMetadata().getFlags().getFlag(EntityFlag.BABY); + mountedHeightOffset = height - (isBaby ? 0.2f : 0.15f); + break; + case PIGLIN: + mountedHeightOffset = height * 0.92f; + break; + case RAVAGER: + mountedHeightOffset = 2.1f; + break; + case SKELETON_HORSE: + mountedHeightOffset -= 0.1875f; + break; + case STRIDER: + mountedHeightOffset = height - 0.19f; + break; + } + return mountedHeightOffset; + } + + private static float getHeightOffset(Entity passenger) { + boolean isBaby; + switch (passenger.getEntityType()) { + case SKELETON: + case STRAY: + case WITHER_SKELETON: + return -0.6f; + case ARMOR_STAND: + if (((ArmorStandEntity) passenger).isMarker()) { + return 0.0f; + } else { + return 0.1f; + } + case ENDERMITE: + case SILVERFISH: + return 0.1f; + case PIGLIN: + case PIGLIN_BRUTE: + case ZOMBIFIED_PIGLIN: + isBaby = passenger.getMetadata().getFlags().getFlag(EntityFlag.BABY); + return isBaby ? -0.05f : -0.45f; + case ZOMBIE: + isBaby = passenger.getMetadata().getFlags().getFlag(EntityFlag.BABY); + return isBaby ? 0.0f : -0.45f; + case EVOKER: + case ILLUSIONER: + case PILLAGER: + case RAVAGER: + case VINDICATOR: + case WITCH: + return -0.45f; + case PLAYER: + return -0.35f; + } + if (passenger instanceof AnimalEntity) { + return 0.14f; + } + return 0f; + } + + /** + * Adjust an entity's height if they have mounted/dismounted an entity. + */ + public static void updateMountOffset(Entity passenger, Entity mount, GeyserSession session, boolean rider, boolean riding, boolean moreThanOneEntity) { + passenger.getMetadata().getFlags().setFlag(EntityFlag.RIDING, riding); + if (riding) { + // Without the Y offset, Bedrock players will find themselves in the floor when mounting + float mountedHeightOffset = getMountedHeightOffset(mount); + float heightOffset = getHeightOffset(passenger); + + float xOffset = 0; + float yOffset = mountedHeightOffset + heightOffset; + float zOffset = 0; + switch (mount.getEntityType()) { + case BOAT: + // Without the X offset, more than one entity on a boat is stacked on top of each other + if (rider && moreThanOneEntity) { + xOffset = 0.2f; + } else if (moreThanOneEntity) { + xOffset = -0.6f; + } + break; + case CHICKEN: + zOffset = -0.1f; + break; + case LLAMA: + zOffset = -0.3f; + break; + } + /* + * Bedrock Differences + * Zoglin & Hoglin seem to be taller in Bedrock edition + * Horses are tinier + * Players, Minecarts, and Boats have different origins + */ + if (passenger.getEntityType() == EntityType.PLAYER && mount.getEntityType() != EntityType.PLAYER) { + yOffset += EntityType.PLAYER.getOffset(); + } + switch (mount.getEntityType()) { + case MINECART: + case MINECART_HOPPER: + case MINECART_TNT: + case MINECART_CHEST: + case MINECART_FURNACE: + case MINECART_SPAWNER: + case MINECART_COMMAND_BLOCK: + case BOAT: + yOffset -= mount.getEntityType().getHeight() * 0.5f; + } + Vector3f offset = Vector3f.from(xOffset, yOffset, zOffset); + passenger.getMetadata().put(EntityData.RIDER_SEAT_POSITION, offset); + } + passenger.updateBedrockMetadata(session); + } } From 3835da288fb2564a851e6199bf3d81528998dafd Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 6 Jun 2021 14:41:41 -0400 Subject: [PATCH 087/107] No need to cast here (thanks Konica) --- .../src/main/java/org/geysermc/connector/entity/Entity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 90d0d36b2..bbe0bee23 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/Entity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/Entity.java @@ -309,7 +309,7 @@ public class Entity { // The value that Java edition gives us is in ticks, but Bedrock uses a float percentage of the strength 0.0 -> 1.0 // The Java client caps its freezing tick percentage at 140 int freezingTicks = Math.min((int) entityMetadata.getValue(), 140); - metadata.put(EntityData.FREEZING_EFFECT_STRENGTH, (freezingTicks / (float) 140)); + metadata.put(EntityData.FREEZING_EFFECT_STRENGTH, (freezingTicks / 140f)); break; } } From 3cdc4c767db77adc3e41f0cd065952294f012afb Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 6 Jun 2021 19:01:16 -0400 Subject: [PATCH 088/107] Don't start if Floodgate is outdated --- .../geysermc/platform/bungeecord/GeyserBungeePlugin.java | 6 ++++++ .../org/geysermc/platform/spigot/GeyserSpigotPlugin.java | 7 +++++++ .../geysermc/platform/velocity/GeyserVelocityPlugin.java | 9 +++++++++ connector/src/main/resources/languages | 2 +- connector/src/main/resources/mappings | 2 +- 5 files changed, 24 insertions(+), 2 deletions(-) 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 c99a47621..84db12341 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 @@ -94,6 +94,12 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { this.geyserLogger = new GeyserBungeeLogger(getLogger(), geyserConfig.isDebugMode()); GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); + // Remove this in like a year + if (getProxy().getPluginManager().getPlugin("floodgate-bungee") != null) { + geyserLogger.severe(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.outdated", "https://ci.opencollab.dev/job/GeyserMC/job/Floodgate/job/master/")); + return; + } + if (geyserConfig.getRemote().getAuthType().equals("floodgate") && getProxy().getPluginManager().getPlugin("floodgate") == null) { geyserLogger.severe(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.disabling")); return; 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 985d6ed0b..5fe4398ba 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 @@ -120,6 +120,13 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { this.geyserLogger = new GeyserSpigotLogger(getLogger(), geyserConfig.isDebugMode()); GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); + // Remove this in like a year + if (Bukkit.getPluginManager().getPlugin("floodgate-bukkit") != null) { + geyserLogger.severe(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.outdated", "https://ci.opencollab.dev/job/GeyserMC/job/Floodgate/job/master/")); + this.getPluginLoader().disablePlugin(this); + return; + } + if (geyserConfig.getRemote().getAuthType().equals("floodgate") && Bukkit.getPluginManager().getPlugin("floodgate") == null) { geyserLogger.severe(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.disabling")); this.getPluginLoader().disablePlugin(this); 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 bcb388ca6..c7b994d48 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 @@ -107,6 +107,15 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { this.geyserLogger = new GeyserVelocityLogger(logger, geyserConfig.isDebugMode()); GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); + // Remove this in like a year + try { + // Should only exist on 1.0 + Class.forName("org.geysermc.floodgate.FloodgateAPI"); + geyserLogger.severe(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.outdated", "https://ci.opencollab.dev/job/GeyserMC/job/Floodgate/job/master/")); + return; + } catch (ClassNotFoundException ignored) { + } + if (geyserConfig.getRemote().getAuthType().equals("floodgate") && !proxyServer.getPluginManager().getPlugin("floodgate").isPresent()) { geyserLogger.severe(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.disabling")); return; diff --git a/connector/src/main/resources/languages b/connector/src/main/resources/languages index bf4bf0554..b66aef5db 160000 --- a/connector/src/main/resources/languages +++ b/connector/src/main/resources/languages @@ -1 +1 @@ -Subproject commit bf4bf0554bd9705f33fe4ccf4aab0ec0075350d4 +Subproject commit b66aef5db3633468f1e6ece408ecd7a3d4a4faeb diff --git a/connector/src/main/resources/mappings b/connector/src/main/resources/mappings index ea0056203..af2d3a85b 160000 --- a/connector/src/main/resources/mappings +++ b/connector/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit ea0056203d62db84a61bee285e0d85d22cb21bff +Subproject commit af2d3a85b04517ba76e72d48eef5bb3b837ec5bb From 715b9ab4b50678c3b8e8708170cb81fc17a1556e Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 6 Jun 2021 19:48:49 -0400 Subject: [PATCH 089/107] Change item names if acting as a replacement --- .../network/translators/item/ItemRegistry.java | 15 ++++++++++++--- .../translators/item/TranslatableItemEntry.java | 2 +- 2 files changed, 13 insertions(+), 4 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 2958e6f00..5bbf379f3 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 @@ -404,13 +404,16 @@ public class ItemRegistry { "", bedrockBlockId, stackSize); } - } else if (entry.getKey().equals("minecraft:spectral_arrow") || entry.getKey().equals("minecraft:knowledge_book")) { - // These items don't exist on Java, so set up a container that indicates they should have custom names + } else if (entry.getKey().equals("minecraft:spectral_arrow") || entry.getKey().equals("minecraft:knowledge_book") + // To remove later... hopefully + || entry.getKey().contains("candle") || entry.getKey().equals("minecraft:bundle") || entry.getKey().equals("minecraft:sculk_sensor")) { + // These items don't exist on Bedrock, so set up a container that indicates they should have custom names itemEntry = new TranslatableItemEntry( entry.getKey(), bedrockIdentifier, itemIndex, bedrockId, entry.getValue().get("bedrock_data").intValue(), bedrockBlockId, stackSize); + GeyserConnector.getInstance().getLogger().debug("Adding " + entry.getKey() + " as an item that needs to be translated."); } else { itemEntry = new ItemEntry( entry.getKey(), bedrockIdentifier, itemIndex, bedrockId, @@ -530,7 +533,13 @@ public class ItemRegistry { Set javaOnlyItems = new ObjectOpenHashSet<>(); Collections.addAll(javaOnlyItems, "minecraft:spectral_arrow", "minecraft:debug_stick", - "minecraft:knowledge_book", "minecraft:tipped_arrow", "minecraft:trader_llama_spawn_egg"); + "minecraft:knowledge_book", "minecraft:tipped_arrow", "minecraft:trader_llama_spawn_egg", + // To be removed in Bedrock 1.17.10... right??? RIGHT??? + "minecraft:candle", "minecraft:white_candle", "minecraft:orange_candle", "minecraft:magenta_candle", + "minecraft:light_blue_candle", "minecraft:yellow_candle", "minecraft:lime_candle", "minecraft:pink_candle", + "minecraft:gray_candle", "minecraft:light_gray_candle", "minecraft:cyan_candle", "minecraft:purple_candle", + "minecraft:blue_candle", "minecraft:brown_candle", "minecraft:green_candle", "minecraft:red_candle", "minecraft:black_candle", + "minecraft:bundle", "minecraft:sculk_sensor"); if (!usingFurnaceMinecart) { javaOnlyItems.add("minecraft:furnace_minecart"); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/TranslatableItemEntry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/TranslatableItemEntry.java index b4ea92a0e..3967abb17 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/TranslatableItemEntry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/TranslatableItemEntry.java @@ -36,6 +36,6 @@ public class TranslatableItemEntry extends ItemEntry { public TranslatableItemEntry(String javaIdentifier, String bedrockIdentifier, int javaId, int bedrockId, int bedrockData, int bedrockBlockId, int stackSize) { super(javaIdentifier, bedrockIdentifier, javaId, bedrockId, bedrockData, bedrockBlockId, stackSize); - this.translationString = "item." + javaIdentifier.replace(":", "."); + this.translationString = (isBlock() ? "block." : "item.") + javaIdentifier.replace(":", "."); } } From ea237f20c96b7e56cbd8e2f78e55959504ce5486 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 6 Jun 2021 21:39:39 -0400 Subject: [PATCH 090/107] Shulker attach position is now irrelevant, it seems --- .../connector/entity/living/monster/ShulkerEntity.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/ShulkerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/ShulkerEntity.java index b2e3f834e..142c0012b 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/ShulkerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/ShulkerEntity.java @@ -48,13 +48,6 @@ public class ShulkerEntity extends GolemEntity { BlockFace blockFace = (BlockFace) entityMetadata.getValue(); metadata.put(EntityData.SHULKER_ATTACH_FACE, (byte) blockFace.ordinal()); } - //TODO - this was removed on Java Edition, but does Bedrock Edition still need it?? -// if (entityMetadata.getId() == 16) { -// Position position = (Position) entityMetadata.getValue(); -// if (position != null) { -// metadata.put(EntityData.SHULKER_ATTACH_POS, Vector3i.from(position.getX(), position.getY(), position.getZ())); -// } -// } if (entityMetadata.getId() == 17) { int height = (byte) entityMetadata.getValue(); From 8510451dde345fed9c5b09fd99e90a93b6e1da6a Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 7 Jun 2021 11:26:23 -0400 Subject: [PATCH 091/107] We're not '1.16.4 - 1.16.5' anymore --- .../src/main/java/org/geysermc/connector/GeyserConnector.java | 3 ++- 1 file changed, 2 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 2ba6182ef..946e699fb 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -28,6 +28,7 @@ package org.geysermc.connector; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.steveice10.mc.protocol.MinecraftConstants; import com.nukkitx.network.raknet.RakNetConstants; import com.nukkitx.network.util.EventLoops; import com.nukkitx.protocol.bedrock.BedrockServer; @@ -93,7 +94,7 @@ public class GeyserConnector { public static final String NAME = "Geyser"; public static final String GIT_VERSION = "DEV"; // A fallback for running in IDEs public static final String VERSION = "DEV"; // A fallback for running in IDEs - public static final String MINECRAFT_VERSION = "1.16.4 - 1.16.5"; + public static final String MINECRAFT_VERSION = MinecraftConstants.GAME_VERSION; // Change if multiple version strings are supported /** * Oauth client ID for Microsoft authentication From a2b3e1946d3f419ea734a98015e4a6b157124785 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 7 Jun 2021 14:19:17 -0400 Subject: [PATCH 092/107] Update lectern if updated via the WindowItemsPacket --- .../network/session/cache/WindowCache.java | 0 .../LecternInventoryTranslator.java | 120 ++++++++++-------- 2 files changed, 65 insertions(+), 55 deletions(-) delete mode 100644 connector/src/main/java/org/geysermc/connector/network/session/cache/WindowCache.java diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/WindowCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/WindowCache.java deleted file mode 100644 index e69de29bb..000000000 diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java index c08dfd995..b15a73493 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java @@ -78,71 +78,81 @@ public class LecternInventoryTranslator extends BaseInventoryTranslator { @Override public void updateInventory(GeyserSession session, Inventory inventory) { + GeyserItemStack itemStack = inventory.getItem(0); + if (!itemStack.isEmpty()) { + updateBook(session, inventory, itemStack); + } } @Override public void updateSlot(GeyserSession session, Inventory inventory, int slot) { this.updater.updateSlot(this, session, inventory, slot); if (slot == 0) { - LecternContainer lecternContainer = (LecternContainer) inventory; - if (session.isDroppingLecternBook()) { - // We have to enter the inventory GUI to eject the book - ClientClickWindowButtonPacket packet = new ClientClickWindowButtonPacket(inventory.getId(), 3); - session.sendDownstreamPacket(packet); - session.setDroppingLecternBook(false); - InventoryUtils.closeInventory(session, inventory.getId(), false); - } else if (lecternContainer.getBlockEntityTag() == null) { - // If the method returns true, this is already handled for us - GeyserItemStack geyserItemStack = inventory.getItem(0); - CompoundTag tag = geyserItemStack.getNbt(); - // Position has to be the last interacted position... right? - Vector3i position = session.getLastInteractionBlockPosition(); - // shouldRefresh means that we should boot out the client on our side because their lectern GUI isn't updated yet - boolean shouldRefresh = !session.getConnector().getWorldManager().shouldExpectLecternHandled() && !session.getLecternCache().contains(position); + updateBook(session, inventory, inventory.getItem(0)); + } + } - NbtMap blockEntityTag; - if (tag != null) { - int pagesSize = ((ListTag) tag.get("pages")).size(); - ItemData itemData = geyserItemStack.getItemData(session); - NbtMapBuilder lecternTag = getBaseLecternTag(position.getX(), position.getY(), position.getZ(), pagesSize); - lecternTag.putCompound("book", NbtMap.builder() - .putByte("Count", (byte) itemData.getCount()) - .putShort("Damage", (short) 0) - .putString("Name", "minecraft:written_book") - .putCompound("tag", itemData.getTag()) - .build()); - lecternTag.putInt("page", lecternContainer.getCurrentBedrockPage()); - blockEntityTag = lecternTag.build(); - } else { - // There is *a* book here, but... no NBT. - NbtMapBuilder lecternTag = getBaseLecternTag(position.getX(), position.getY(), position.getZ(), 1); - NbtMapBuilder bookTag = NbtMap.builder() - .putByte("Count", (byte) 1) - .putShort("Damage", (short) 0) - .putString("Name", "minecraft:writable_book") - .putCompound("tag", NbtMap.builder().putList("pages", NbtType.COMPOUND, Collections.singletonList( - NbtMap.builder() + /** + * Translate the data of the book in the lectern into a block entity tag. + */ + private void updateBook(GeyserSession session, Inventory inventory, GeyserItemStack book) { + LecternContainer lecternContainer = (LecternContainer) inventory; + if (session.isDroppingLecternBook()) { + // We have to enter the inventory GUI to eject the book + ClientClickWindowButtonPacket packet = new ClientClickWindowButtonPacket(inventory.getId(), 3); + session.sendDownstreamPacket(packet); + session.setDroppingLecternBook(false); + InventoryUtils.closeInventory(session, inventory.getId(), false); + } else if (lecternContainer.getBlockEntityTag() == null) { + CompoundTag tag = book.getNbt(); + // Position has to be the last interacted position... right? + Vector3i position = session.getLastInteractionBlockPosition(); + // If shouldExpectLecternHandled returns true, this is already handled for us + // shouldRefresh means that we should boot out the client on our side because their lectern GUI isn't updated yet + boolean shouldRefresh = !session.getConnector().getWorldManager().shouldExpectLecternHandled() && !session.getLecternCache().contains(position); + + NbtMap blockEntityTag; + if (tag != null) { + int pagesSize = ((ListTag) tag.get("pages")).size(); + ItemData itemData = book.getItemData(session); + NbtMapBuilder lecternTag = getBaseLecternTag(position.getX(), position.getY(), position.getZ(), pagesSize); + lecternTag.putCompound("book", NbtMap.builder() + .putByte("Count", (byte) itemData.getCount()) + .putShort("Damage", (short) 0) + .putString("Name", "minecraft:written_book") + .putCompound("tag", itemData.getTag()) + .build()); + lecternTag.putInt("page", lecternContainer.getCurrentBedrockPage()); + blockEntityTag = lecternTag.build(); + } else { + // There is *a* book here, but... no NBT. + NbtMapBuilder lecternTag = getBaseLecternTag(position.getX(), position.getY(), position.getZ(), 1); + NbtMapBuilder bookTag = NbtMap.builder() + .putByte("Count", (byte) 1) + .putShort("Damage", (short) 0) + .putString("Name", "minecraft:writable_book") + .putCompound("tag", NbtMap.builder().putList("pages", NbtType.COMPOUND, Collections.singletonList( + NbtMap.builder() .putString("photoname", "") .putString("text", "") - .build() - )).build()); + .build() + )).build()); - blockEntityTag = lecternTag.putCompound("book", bookTag.build()).build(); - } - - // Even with serverside access to lecterns, we don't easily know which lectern this is, so we need to rebuild - // the block entity tag - lecternContainer.setBlockEntityTag(blockEntityTag); - lecternContainer.setPosition(position); - if (shouldRefresh) { - // Update the lectern because it's not updated client-side - BlockEntityUtils.updateBlockEntity(session, blockEntityTag, position); - session.getLecternCache().add(position); - // Close the window - we will reopen it once the client has this data synced - ClientCloseWindowPacket closeWindowPacket = new ClientCloseWindowPacket(lecternContainer.getId()); - session.sendDownstreamPacket(closeWindowPacket); - InventoryUtils.closeInventory(session, inventory.getId(), false); - } + blockEntityTag = lecternTag.putCompound("book", bookTag.build()).build(); + } + + // Even with serverside access to lecterns, we don't easily know which lectern this is, so we need to rebuild + // the block entity tag + lecternContainer.setBlockEntityTag(blockEntityTag); + lecternContainer.setPosition(position); + if (shouldRefresh) { + // Update the lectern because it's not updated client-side + BlockEntityUtils.updateBlockEntity(session, blockEntityTag, position); + session.getLecternCache().add(position); + // Close the window - we will reopen it once the client has this data synced + ClientCloseWindowPacket closeWindowPacket = new ClientCloseWindowPacket(lecternContainer.getId()); + session.sendDownstreamPacket(closeWindowPacket); + InventoryUtils.closeInventory(session, inventory.getId(), false); } } } From b41552faf3c0bf7b3e75eecafec623d77b75b988 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 7 Jun 2021 14:56:29 -0400 Subject: [PATCH 093/107] Use ViaVersion master branch --- bootstrap/spigot/pom.xml | 2 +- .../java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bootstrap/spigot/pom.xml b/bootstrap/spigot/pom.xml index c4286be9f..84086ba15 100644 --- a/bootstrap/spigot/pom.xml +++ b/bootstrap/spigot/pom.xml @@ -33,7 +33,7 @@ com.viaversion viaversion - 4.0.0-1.17-pre3-SNAPSHOT + 4.0.0 provided diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java index 5fe4398ba..881a9caf9 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 @@ -159,7 +159,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { Class.forName("com.viaversion.viaversion.api.ViaManager"); } catch (ClassNotFoundException e) { geyserLogger.warning(LanguageUtils.getLocaleStringLog("geyser.bootstrap.viaversion.too_old", - "https://ci.viaversion.com/job/ViaVersion-DEV/")); + "https://ci.viaversion.com/job/ViaVersion/")); isViaVersion = false; if (this.geyserConfig.isDebugMode()) { e.printStackTrace(); From 01d764829678074116118fdccc91e44bc01a3352 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 7 Jun 2021 19:16:36 -0400 Subject: [PATCH 094/107] Implement new entity statuses and goat milk sounds --- connector/pom.xml | 2 +- .../entity/living/animal/GoatEntity.java | 9 ++++++++- .../entity/JavaEntityStatusTranslator.java | 20 ++++++++++++++++++- ...=> MilkEntitySoundInteractionHandler.java} | 19 ++++++++++++++---- 4 files changed, 43 insertions(+), 7 deletions(-) rename connector/src/main/java/org/geysermc/connector/network/translators/sound/entity/{MilkCowSoundInteractionHandler.java => MilkEntitySoundInteractionHandler.java} (78%) diff --git a/connector/pom.xml b/connector/pom.xml index 471ce3415..fc8f82cd9 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -148,7 +148,7 @@ com.github.GeyserMC MCProtocolLib - bc06ae5 + 2bd966a diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/GoatEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/GoatEntity.java index e0c5066d6..a43998f27 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/GoatEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/GoatEntity.java @@ -29,6 +29,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadat import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import lombok.Getter; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; @@ -36,6 +37,9 @@ public class GoatEntity extends AnimalEntity { private static final float LONG_JUMPING_HEIGHT = 1.3f * 0.7f; private static final float LONG_JUMPING_WIDTH = 0.9f * 0.7f; + @Getter + private boolean isScreamer; + public GoatEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); } @@ -43,7 +47,10 @@ public class GoatEntity extends AnimalEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { super.updateBedrockMetadata(entityMetadata, session); - + if (entityMetadata.getId() == 17) { + // Not used in Bedrock Edition + isScreamer = (boolean) entityMetadata.getValue(); + } } @Override 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 e33ef7bf1..f444d3ac1 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 @@ -94,6 +94,7 @@ public class JavaEntityStatusTranslator extends PacketTranslator Date: Mon, 7 Jun 2021 21:23:46 -0400 Subject: [PATCH 095/107] Bump Adventure to 4.8.0 --- connector/pom.xml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/connector/pom.xml b/connector/pom.xml index fc8f82cd9..31e472c9f 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -11,7 +11,7 @@ connector - 4.7.0 + 4.8.0 8.5.2 2.10.2 4.1.59.Final @@ -145,6 +145,12 @@ 29.0-jre compile + + com.github.GeyserMC + MCAuthLib + 0e48a094f2 + compile + com.github.GeyserMC MCProtocolLib @@ -240,6 +246,7 @@ 2.1.3 compile + net.kyori adventure-api @@ -270,12 +277,6 @@ 4.13.1 test - - com.github.GeyserMC - MCAuthLib - 0e48a094f2 - compile - From 6b681213ed596bd5c54ae86f0fd752cf19e688ae Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 7 Jun 2021 21:33:39 -0400 Subject: [PATCH 096/107] Going to assume Adventure changed something --- .../network/translators/chat/MessageTranslatorTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/src/test/java/org/geysermc/connector/network/translators/chat/MessageTranslatorTest.java b/connector/src/test/java/org/geysermc/connector/network/translators/chat/MessageTranslatorTest.java index 7052123fe..efa1a8b3d 100644 --- a/connector/src/test/java/org/geysermc/connector/network/translators/chat/MessageTranslatorTest.java +++ b/connector/src/test/java/org/geysermc/connector/network/translators/chat/MessageTranslatorTest.java @@ -46,7 +46,7 @@ public class MessageTranslatorTest { // RGB downgrade test messages.put("{\"extra\":[{\"text\":\" \"},{\"color\":\"gold\",\"text\":\"The \"},{\"color\":\"#E14248\",\"obfuscated\":true,\"text\":\"||\"},{\"color\":\"#3AA9FF\",\"bold\":true,\"text\":\"CubeCraft\"},{\"color\":\"#E14248\",\"obfuscated\":true,\"text\":\"||\"},{\"color\":\"gold\",\"text\":\" Network \"},{\"color\":\"green\",\"text\":\"[1.8/1.9+]\\n \"},{\"color\":\"#f5e342\",\"text\":\"✦ \"},{\"color\":\"#b042f5\",\"bold\":true,\"text\":\"N\"},{\"color\":\"#c142f5\",\"bold\":true,\"text\":\"E\"},{\"color\":\"#d342f5\",\"bold\":true,\"text\":\"W\"},{\"color\":\"#e442f5\",\"bold\":true,\"text\":\":\"},{\"color\":\"#f542f5\",\"bold\":true,\"text\":\" \"},{\"color\":\"#bcf542\",\"bold\":true,\"text\":\"A\"},{\"color\":\"#acee3f\",\"bold\":true,\"text\":\"M\"},{\"color\":\"#9ce73c\",\"bold\":true,\"text\":\"O\"},{\"color\":\"#8ce039\",\"bold\":true,\"text\":\"N\"},{\"color\":\"#7cd936\",\"bold\":true,\"text\":\"G\"},{\"color\":\"#6cd233\",\"bold\":true,\"text\":\" \"},{\"color\":\"#5ccb30\",\"bold\":true,\"text\":\"S\"},{\"color\":\"#4cc42d\",\"bold\":true,\"text\":\"L\"},{\"color\":\"#3cbd2a\",\"bold\":true,\"text\":\"I\"},{\"color\":\"#2cb627\",\"bold\":true,\"text\":\"M\"},{\"color\":\"#1caf24\",\"bold\":true,\"text\":\"E\"},{\"color\":\"#0ca821\",\"bold\":true,\"text\":\"S\"},{\"color\":\"#f5e342\",\"text\":\" \"},{\"color\":\"#6d7c87\",\"text\":\"(kinda sus) \"},{\"color\":\"#f5e342\",\"text\":\"✦\"}],\"text\":\"\"}", - " §r§6The §r§d§k||§r§b§lCubeCraft§r§d§k||§r§6 Network §r§a[1.8/1.9+]\n" + + " §r§6The §r§c§k||§r§b§lCubeCraft§r§c§k||§r§6 Network §r§a[1.8/1.9+]\n" + " §r§e✦ §r§d§lN§r§d§lE§r§d§lW§r§d§l:§r§d§l §r§e§lA§r§e§lM§r§e§lO§r§a§lN§r§a§lG§r§a§l §r§a§lS§r§2§lL§r§2§lI§r§2§lM§r§2§lE§r§2§lS§r§e §r§b(kinda sus) §r§e✦"); // Color code format resetting From bb20afb1232fa1b15c09eab88272269ead71d36f Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 7 Jun 2021 23:09:42 -0400 Subject: [PATCH 097/107] Warn if server is using high/low dimension heights and remove translations on some strings --- .../network/session/cache/ChunkCache.java | 8 +++- .../network/translators/BiomeTranslator.java | 3 +- .../translators/EntityIdentifierRegistry.java | 3 +- .../translators/item/ItemRegistry.java | 6 +-- .../translators/item/RecipeRegistry.java | 3 +- .../java/JavaJoinGameTranslator.java | 4 ++ .../java/JavaRespawnTranslator.java | 3 ++ .../geysermc/connector/utils/ChunkUtils.java | 46 +++++++++++++++++-- .../geysermc/connector/utils/FileUtils.java | 2 +- connector/src/main/resources/languages | 2 +- 10 files changed, 63 insertions(+), 17 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 a925b43b9..e9d97d66d 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 @@ -89,7 +89,7 @@ public class ChunkCache { return; } - Chunk chunk = column.getChunks()[y >> 4]; + Chunk chunk = column.getChunks()[(y >> 4) - getChunkMinY()]; if (chunk != null) { chunk.set(x & 0xF, y & 0xF, z & 0xF, block); } @@ -110,7 +110,7 @@ public class ChunkCache { return BlockTranslator.JAVA_AIR_ID; } - Chunk chunk = column.getChunks()[y >> 4]; + Chunk chunk = column.getChunks()[(y >> 4) - getChunkMinY()]; if (chunk != null) { return chunk.get(x & 0xF, y & 0xF, z & 0xF); } @@ -126,4 +126,8 @@ public class ChunkCache { long chunkPosition = MathUtils.chunkPositionToLong(chunkX, chunkZ); chunks.remove(chunkPosition); } + + public int getChunkMinY() { + return minY >> 4; + } } 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 eed901cdf..d797381ce 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 @@ -30,7 +30,6 @@ import com.nukkitx.nbt.NbtMap; 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 +58,7 @@ public class BiomeTranslator { biomesTag = (NbtMap) biomenbtInputStream.readTag(); BIOMES = biomesTag; } catch (Exception ex) { - GeyserConnector.getInstance().getLogger().warning(LanguageUtils.getLocaleStringLog("geyser.toolbox.fail.biome_read")); + GeyserConnector.getInstance().getLogger().warning("Failed to get biomes from biome definitions, is there something wrong with the file?"); 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 fb6d5b93d..5bb029882 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 @@ -29,7 +29,6 @@ import com.nukkitx.nbt.NBTInputStream; 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 +53,7 @@ public class EntityIdentifierRegistry { try (NBTInputStream nbtInputStream = NbtUtils.createNetworkReader(stream)) { ENTITY_IDENTIFIERS = (NbtMap) nbtInputStream.readTag(); } catch (Exception e) { - throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.toolbox.fail.entity"), e); + throw new AssertionError("Unable to get entities from entity identifiers", e); } } } 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 5bbf379f3..f428d37e9 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 @@ -148,7 +148,7 @@ public class ItemRegistry { try { itemEntries = GeyserConnector.JSON_MAPPER.readValue(stream, itemEntriesType); } catch (Exception e) { - throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.toolbox.fail.runtime_bedrock"), e); + throw new AssertionError("Unable to load Bedrock runtime item IDs", e); } int lodestoneCompassId = 0; @@ -172,7 +172,7 @@ public class ItemRegistry { try { creativeItemEntries = GeyserConnector.JSON_MAPPER.readTree(stream).get("items"); } catch (Exception e) { - throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.toolbox.fail.creative"), e); + throw new AssertionError("Unable to load creative items", e); } int netId = 1; @@ -247,7 +247,7 @@ public class ItemRegistry { try { items = GeyserConnector.JSON_MAPPER.readTree(stream); } catch (Exception e) { - throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.toolbox.fail.runtime_java"), e); + throw new AssertionError("Unable to load Java runtime item IDs", e); } BlockTranslator blockTranslator = BlockTranslator1_17_0.INSTANCE; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/RecipeRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/RecipeRegistry.java index 8f7a4a8a2..506317ab9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/RecipeRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/RecipeRegistry.java @@ -41,7 +41,6 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectArrayList; 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; @@ -144,7 +143,7 @@ public class RecipeRegistry { try { items = GeyserConnector.JSON_MAPPER.readTree(stream); } catch (Exception e) { - throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.toolbox.fail.runtime_java"), e); + throw new AssertionError("Unable to load Java runtime item IDs", e); } for (JsonNode entry : items.get("leather_armor")) { 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 dceb13789..53ebd92ff 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 @@ -39,6 +39,7 @@ import org.geysermc.connector.entity.player.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.ChunkUtils; import org.geysermc.connector.utils.DimensionUtils; import org.geysermc.connector.utils.PluginMessageUtils; @@ -53,6 +54,9 @@ public class JavaJoinGameTranslator extends PacketTranslator session.setThunder(false); } + ChunkUtils.applyDimensionHeight(session, packet.getDimension()); + String newDimension = DimensionUtils.getNewDimension(packet.getDimension()); if (!session.getDimension().equals(newDimension) || !packet.getWorldName().equals(session.getWorldName())) { if (!packet.getWorldName().equals(session.getWorldName()) && session.getDimension().equals(newDimension)) { 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 3fd38d508..9723cd53c 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java @@ -32,6 +32,7 @@ import com.github.steveice10.mc.protocol.data.game.chunk.palette.GlobalPalette; import com.github.steveice10.mc.protocol.data.game.chunk.palette.Palette; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.IntTag; import com.github.steveice10.opennbt.tag.builtin.StringTag; import com.github.steveice10.opennbt.tag.builtin.Tag; import com.nukkitx.math.vector.Vector2i; @@ -67,6 +68,14 @@ import static org.geysermc.connector.network.translators.world.block.BlockTransl @UtilityClass public class ChunkUtils { + /** + * The minimum height Bedrock Edition will accept. + */ + private static final int MINIMUM_ACCEPTED_HEIGHT = 0; + /** + * The maximum height Bedrock Edition will accept. + */ + private static final int MAXIMUM_ACCEPTED_HEIGHT = 256; private static int indexYZXtoXZY(int yzx) { return (yzx >> 8) | (yzx & 0x0F0) | ((yzx & 0x00F) << 8); @@ -74,7 +83,9 @@ public class ChunkUtils { public static ChunkData translateToBedrock(GeyserSession session, Column column) { Chunk[] javaSections = column.getChunks(); - ChunkSection[] sections = new ChunkSection[javaSections.length]; + // Ensure that, if the player is using lower world heights, the position is not offset + int yOffset = session.getChunkCache().getChunkMinY(); + ChunkSection[] sections = new ChunkSection[javaSections.length - yOffset]; // Temporarily stores compound tags of Bedrock-only block entities List bedrockOnlyBlockEntities = new ArrayList<>(); @@ -83,6 +94,11 @@ public class ChunkUtils { BitSet pistonOrFlowerPaletteIds = new BitSet(); for (int sectionY = 0; sectionY < javaSections.length; sectionY++) { + if (yOffset < 0 && sectionY < -yOffset) { + // Ignore this chunk since it goes below the accepted height limit + continue; + } + Chunk javaSection = javaSections[sectionY]; // No need to encode an empty section... @@ -114,7 +130,7 @@ public class ChunkUtils { )); } } - sections[sectionY] = section; + sections[sectionY + yOffset] = section; continue; } @@ -187,7 +203,7 @@ public class ChunkUtils { layers = new BlockStorage[]{ layer0, new BlockStorage(BitArrayVersion.V1.createArray(BlockStorage.SIZE, layer1Data), layer1Palette) }; } - sections[sectionY] = new ChunkSection(layers); + sections[sectionY + yOffset] = new ChunkSection(layers); } CompoundTag[] blockEntities = column.getTileEntities(); @@ -220,7 +236,7 @@ public class ChunkUtils { // Get Java blockstate ID from block entity position int blockState = 0; - Chunk section = column.getChunks()[pos.getY() >> 4]; + Chunk section = column.getChunks()[(pos.getY() >> 4) - yOffset]; if (section != null) { blockState = section.get(pos.getX() & 0xF, pos.getY() & 0xF, pos.getZ() & 0xF); } @@ -385,6 +401,28 @@ public class ChunkUtils { } } + /** + * Process the minimum and maximum heights for this dimension + */ + public static void applyDimensionHeight(GeyserSession session, CompoundTag dimensionTag) { + int minY = ((IntTag) dimensionTag.get("min_y")).getValue(); + int maxY = ((IntTag) dimensionTag.get("height")).getValue(); + // Logical height can be ignored probably - seems to be for artificial limits like the Nether. + + if (minY % 16 != 0) { + throw new RuntimeException("Minimum Y must be a multiple of 16!"); + } + if (maxY % 16 != 0) { + throw new RuntimeException("Maximum Y must be a multiple of 16!"); + } + + if (minY < MINIMUM_ACCEPTED_HEIGHT || maxY > MAXIMUM_ACCEPTED_HEIGHT) { + session.getConnector().getLogger().warning(LanguageUtils.getLocaleStringLog("geyser.network.translator.chunk.out_of_bounds")); + } + + session.getChunkCache().setMinY(minY); + } + @Data public static final class ChunkData { private final ChunkSection[] sections; 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 a96d29da2..eecae8e4f 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java @@ -152,7 +152,7 @@ public class FileUtils { public static InputStream getResource(String resource) { InputStream stream = FileUtils.class.getClassLoader().getResourceAsStream(resource); if (stream == null) { - throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.toolbox.fail.resource", resource)); + throw new AssertionError("Unable to find resource: " + resource); } return stream; } diff --git a/connector/src/main/resources/languages b/connector/src/main/resources/languages index b66aef5db..fcc8f01e2 160000 --- a/connector/src/main/resources/languages +++ b/connector/src/main/resources/languages @@ -1 +1 @@ -Subproject commit b66aef5db3633468f1e6ece408ecd7a3d4a4faeb +Subproject commit fcc8f01e25e481c9c82f2ee9bff23111ec781dd7 From 4d3392c16ab1cb0cc51b4bed6885e3557c40a98d Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 7 Jun 2021 23:54:48 -0400 Subject: [PATCH 098/107] Update to 1.17 release --- README.md | 2 +- connector/pom.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7db62fbb2..f15b36e28 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have joined us here! -### Currently supporting Minecraft Bedrock v1.17.0.58 and Minecraft Java 1.17-rc1. +### Currently supporting Minecraft Bedrock 1.17 and Minecraft Java 1.17. ## Setting Up Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set up Geyser. diff --git a/connector/pom.xml b/connector/pom.xml index 31e472c9f..d91b27d47 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -154,10 +154,10 @@ com.github.GeyserMC MCProtocolLib - 2bd966a + e316986 - + compile From 41c1be42628766e23170030986ae767e536d61f3 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Tue, 8 Jun 2021 08:17:12 +0200 Subject: [PATCH 099/107] Only listen for Geyser news --- .../main/java/org/geysermc/connector/utils/Constants.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/utils/Constants.java b/connector/src/main/java/org/geysermc/connector/utils/Constants.java index a0cc9bf80..2a47a5bce 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/Constants.java +++ b/connector/src/main/java/org/geysermc/connector/utils/Constants.java @@ -27,18 +27,14 @@ package org.geysermc.connector.utils; import java.net.URI; import java.net.URISyntaxException; -import java.util.Arrays; import java.util.Collections; -import java.util.HashSet; import java.util.Set; public final class Constants { public static final URI GLOBAL_API_WS_URI; public static final String NTP_SERVER = "time.cloudflare.com"; - public static final Set NEWS_PROJECT_LIST = Collections.unmodifiableSet( - new HashSet<>(Arrays.asList("geyser", "floodgate")) - ); + public static final Set NEWS_PROJECT_LIST = Collections.singleton("geyser"); public static final String NEWS_OVERVIEW_URL = "https://api.geysermc.org/v1/news"; From 1eef152f43992e0ccfb16b72df8236d3a268b71c Mon Sep 17 00:00:00 2001 From: Tim203 Date: Tue, 8 Jun 2021 10:04:40 +0200 Subject: [PATCH 100/107] Added Java Version to the metrics --- .../geysermc/connector/GeyserConnector.java | 36 +++++++++++++++++++ .../geysermc/connector/metrics/Metrics.java | 5 --- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 946e699fb..1d11c52d9 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -81,6 +81,8 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; @Getter public class GeyserConnector { @@ -330,6 +332,40 @@ public class GeyserConnector { return versionMap; })); } + + // The following code can be attributed to the PaperMC project + // https://github.com/PaperMC/Paper/blob/master/Spigot-Server-Patches/0005-Paper-Metrics.patch#L614 + metrics.addCustomChart(new Metrics.DrilldownPie("javaVersion", () -> { + Map> map = new HashMap<>(); + String javaVersion = System.getProperty("java.version"); + Map entry = new HashMap<>(); + entry.put(javaVersion, 1); + + // http://openjdk.java.net/jeps/223 + // Java decided to change their versioning scheme and in doing so modified the + // java.version system property to return $major[.$minor][.$security][-ea], as opposed to + // 1.$major.0_$identifier we can handle pre-9 by checking if the "major" is equal to "1", + // otherwise, 9+ + String majorVersion = javaVersion.split("\\.")[0]; + String release; + + int indexOf = javaVersion.lastIndexOf('.'); + + if (majorVersion.equals("1")) { + release = "Java " + javaVersion.substring(0, indexOf); + } else { + // of course, it really wouldn't be all that simple if they didn't add a quirk, now + // would it valid strings for the major may potentially include values such as -ea to + // denote a pre release + Matcher versionMatcher = Pattern.compile("\\d+").matcher(majorVersion); + if (versionMatcher.find()) { + majorVersion = versionMatcher.group(0); + } + release = "Java " + majorVersion; + } + map.put(release, entry); + return map; + })); } boolean isGui = false; diff --git a/connector/src/main/java/org/geysermc/connector/metrics/Metrics.java b/connector/src/main/java/org/geysermc/connector/metrics/Metrics.java index 1457780b1..6ebca3f8c 100644 --- a/connector/src/main/java/org/geysermc/connector/metrics/Metrics.java +++ b/connector/src/main/java/org/geysermc/connector/metrics/Metrics.java @@ -152,20 +152,15 @@ public class Metrics { */ private ObjectNode getServerData() { // OS specific data - int playerAmount = connector.getPlayers().size(); - String osName = System.getProperty("os.name"); String osArch = System.getProperty("os.arch"); String osVersion = System.getProperty("os.version"); - String javaVersion = System.getProperty("java.version"); int coreCount = Runtime.getRuntime().availableProcessors(); ObjectNode data = mapper.createObjectNode(); data.put("serverUUID", serverUUID); - data.put("playerAmount", playerAmount); - data.put("javaVersion", javaVersion); data.put("osName", osName); data.put("osArch", osArch); data.put("osVersion", osVersion); From 68cd2eb4f1f66aaafabd02a33a5ed0ad8b07ae24 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Tue, 8 Jun 2021 10:12:52 +0200 Subject: [PATCH 101/107] Moved Floodgate dump info to one class --- .../floodgate/util/FloodgateConfigHolder.java | 35 ------------------- ...esHolder.java => FloodgateInfoHolder.java} | 6 +++- .../org/geysermc/connector/dump/DumpInfo.java | 7 ++-- 3 files changed, 8 insertions(+), 40 deletions(-) delete mode 100644 common/src/main/java/org/geysermc/floodgate/util/FloodgateConfigHolder.java rename common/src/main/java/org/geysermc/floodgate/util/{FloodgateGitPropertiesHolder.java => FloodgateInfoHolder.java} (93%) diff --git a/common/src/main/java/org/geysermc/floodgate/util/FloodgateConfigHolder.java b/common/src/main/java/org/geysermc/floodgate/util/FloodgateConfigHolder.java deleted file mode 100644 index d33840bb4..000000000 --- a/common/src/main/java/org/geysermc/floodgate/util/FloodgateConfigHolder.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.floodgate.util; - -import lombok.Getter; -import lombok.Setter; - -public final class FloodgateConfigHolder { - @Getter - @Setter - private static Object config; -} diff --git a/common/src/main/java/org/geysermc/floodgate/util/FloodgateGitPropertiesHolder.java b/common/src/main/java/org/geysermc/floodgate/util/FloodgateInfoHolder.java similarity index 93% rename from common/src/main/java/org/geysermc/floodgate/util/FloodgateGitPropertiesHolder.java rename to common/src/main/java/org/geysermc/floodgate/util/FloodgateInfoHolder.java index 5f157aceb..c7a681f9d 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/FloodgateGitPropertiesHolder.java +++ b/common/src/main/java/org/geysermc/floodgate/util/FloodgateInfoHolder.java @@ -27,9 +27,13 @@ package org.geysermc.floodgate.util; import lombok.Getter; import lombok.Setter; + import java.util.Properties; -public class FloodgateGitPropertiesHolder { +public final class FloodgateInfoHolder { + @Getter + @Setter + private static Object config; @Getter @Setter private static Properties gitProperties; diff --git a/connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java b/connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java index f83598469..fee4ac7a4 100644 --- a/connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java +++ b/connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java @@ -42,8 +42,7 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.utils.DockerCheck; import org.geysermc.connector.utils.FileUtils; import org.geysermc.floodgate.util.DeviceOs; -import org.geysermc.floodgate.util.FloodgateConfigHolder; -import org.geysermc.floodgate.util.FloodgateGitPropertiesHolder; +import org.geysermc.floodgate.util.FloodgateInfoHolder; import java.io.File; import java.io.IOException; @@ -184,8 +183,8 @@ public class DumpInfo { private final Object config; Floodgate() { - this.gitInfo = FloodgateGitPropertiesHolder.getGitProperties(); - this.config = FloodgateConfigHolder.getConfig(); + this.gitInfo = FloodgateInfoHolder.getGitProperties(); + this.config = FloodgateInfoHolder.getConfig(); } } From acc3a8222ac1e90e3b70c67df8db8ac3773674de Mon Sep 17 00:00:00 2001 From: Tim203 Date: Tue, 8 Jun 2021 12:53:00 +0200 Subject: [PATCH 102/107] Check if item is active and added global items --- .../src/main/java/org/geysermc/floodgate/news/NewsItem.java | 4 ++++ .../main/java/org/geysermc/connector/utils/NewsHandler.java | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/org/geysermc/floodgate/news/NewsItem.java b/common/src/main/java/org/geysermc/floodgate/news/NewsItem.java index 2f6bb4852..be0634a6d 100644 --- a/common/src/main/java/org/geysermc/floodgate/news/NewsItem.java +++ b/common/src/main/java/org/geysermc/floodgate/news/NewsItem.java @@ -100,6 +100,10 @@ public final class NewsItem { return project; } + public boolean isGlobal() { + return "all".equals(getProject()); + } + public boolean isActive() { return active; } diff --git a/connector/src/main/java/org/geysermc/connector/utils/NewsHandler.java b/connector/src/main/java/org/geysermc/connector/utils/NewsHandler.java index aa8fe2efc..fb945edc6 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/NewsHandler.java +++ b/connector/src/main/java/org/geysermc/connector/utils/NewsHandler.java @@ -142,7 +142,11 @@ public class NewsHandler { return; } - if (!Constants.NEWS_PROJECT_LIST.contains(item.getProject())) { + if (!item.isActive()) { + return; + } + + if (!item.isGlobal() && !Constants.NEWS_PROJECT_LIST.contains(item.getProject())) { return; } From f0b8870a47b661cf84b51f127bf81272268616d7 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Tue, 8 Jun 2021 13:21:22 +0200 Subject: [PATCH 103/107] 'on server started' should only work during the first check --- .../java/org/geysermc/connector/utils/Constants.java | 5 +---- .../org/geysermc/connector/utils/NewsHandler.java | 11 ++++------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/utils/Constants.java b/connector/src/main/java/org/geysermc/connector/utils/Constants.java index 2a47a5bce..02f5c1ae4 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/Constants.java +++ b/connector/src/main/java/org/geysermc/connector/utils/Constants.java @@ -27,16 +27,13 @@ package org.geysermc.connector.utils; import java.net.URI; import java.net.URISyntaxException; -import java.util.Collections; -import java.util.Set; public final class Constants { public static final URI GLOBAL_API_WS_URI; public static final String NTP_SERVER = "time.cloudflare.com"; - public static final Set NEWS_PROJECT_LIST = Collections.singleton("geyser"); - public static final String NEWS_OVERVIEW_URL = "https://api.geysermc.org/v1/news"; + public static final String NEWS_PROJECT_NAME = "geyser"; static { URI wsUri = null; diff --git a/connector/src/main/java/org/geysermc/connector/utils/NewsHandler.java b/connector/src/main/java/org/geysermc/connector/utils/NewsHandler.java index fb945edc6..c70255f52 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/NewsHandler.java +++ b/connector/src/main/java/org/geysermc/connector/utils/NewsHandler.java @@ -52,7 +52,7 @@ public class NewsHandler { private final String branch; private final int build; - private boolean geyserStarted; + private boolean firstCheck = true; public NewsHandler(String branch, int build) { this.branch = branch; @@ -77,6 +77,7 @@ public class NewsHandler { addNews(newsItem); } } + firstCheck = false; } catch (Exception e) { if (logger.isDebug()) { logger.error("Error while reading news item", e); @@ -85,10 +86,6 @@ public class NewsHandler { } catch (JsonSyntaxException ignored) {} } - public void setGeyserStarted() { - geyserStarted = true; - } - public void handleNews(GeyserSession session, NewsItemAction action) { for (NewsItem news : getActiveNews(action)) { handleNewsItem(session, news, action); @@ -98,7 +95,7 @@ public class NewsHandler { private void handleNewsItem(GeyserSession session, NewsItem news, NewsItemAction action) { switch (action) { case ON_SERVER_STARTED: - if (!geyserStarted) { + if (!firstCheck) { return; } case BROADCAST_TO_CONSOLE: @@ -146,7 +143,7 @@ public class NewsHandler { return; } - if (!item.isGlobal() && !Constants.NEWS_PROJECT_LIST.contains(item.getProject())) { + if (!item.isGlobal() && !Constants.NEWS_PROJECT_NAME.equals(item.getProject())) { return; } From 511cfd1ae8a05eaeb8adc551980d6808c1998fee Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 8 Jun 2021 08:55:56 -0400 Subject: [PATCH 104/107] Update Geyser version to 1.4.0-SNAPSHOT --- 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 ++-- pom.xml | 2 +- 9 files changed, 15 insertions(+), 15 deletions(-) diff --git a/bootstrap/bungeecord/pom.xml b/bootstrap/bungeecord/pom.xml index 5e15162b8..710c8176e 100644 --- a/bootstrap/bungeecord/pom.xml +++ b/bootstrap/bungeecord/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 1.3.0-SNAPSHOT + 1.4.0-SNAPSHOT bootstrap-bungeecord @@ -14,7 +14,7 @@ org.geysermc connector - 1.3.0-SNAPSHOT + 1.4.0-SNAPSHOT compile diff --git a/bootstrap/pom.xml b/bootstrap/pom.xml index d58cb2ca4..bf31f3fea 100644 --- a/bootstrap/pom.xml +++ b/bootstrap/pom.xml @@ -6,7 +6,7 @@ org.geysermc geyser-parent - 1.3.0-SNAPSHOT + 1.4.0-SNAPSHOT bootstrap-parent pom diff --git a/bootstrap/spigot/pom.xml b/bootstrap/spigot/pom.xml index 84086ba15..4ac236529 100644 --- a/bootstrap/spigot/pom.xml +++ b/bootstrap/spigot/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 1.3.0-SNAPSHOT + 1.4.0-SNAPSHOT bootstrap-spigot @@ -21,7 +21,7 @@ org.geysermc connector - 1.3.0-SNAPSHOT + 1.4.0-SNAPSHOT compile diff --git a/bootstrap/sponge/pom.xml b/bootstrap/sponge/pom.xml index adeaa91de..72733a311 100644 --- a/bootstrap/sponge/pom.xml +++ b/bootstrap/sponge/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 1.3.0-SNAPSHOT + 1.4.0-SNAPSHOT bootstrap-sponge @@ -14,7 +14,7 @@ org.geysermc connector - 1.3.0-SNAPSHOT + 1.4.0-SNAPSHOT compile diff --git a/bootstrap/standalone/pom.xml b/bootstrap/standalone/pom.xml index 6610af3b5..2d28a7535 100644 --- a/bootstrap/standalone/pom.xml +++ b/bootstrap/standalone/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 1.3.0-SNAPSHOT + 1.4.0-SNAPSHOT bootstrap-standalone @@ -14,7 +14,7 @@ org.geysermc connector - 1.3.0-SNAPSHOT + 1.4.0-SNAPSHOT compile diff --git a/bootstrap/velocity/pom.xml b/bootstrap/velocity/pom.xml index 3b093285d..0190d128e 100644 --- a/bootstrap/velocity/pom.xml +++ b/bootstrap/velocity/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 1.3.0-SNAPSHOT + 1.4.0-SNAPSHOT bootstrap-velocity @@ -14,7 +14,7 @@ org.geysermc connector - 1.3.0-SNAPSHOT + 1.4.0-SNAPSHOT compile diff --git a/common/pom.xml b/common/pom.xml index be90f0146..8ddcfd981 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -6,7 +6,7 @@ org.geysermc geyser-parent - 1.3.0-SNAPSHOT + 1.4.0-SNAPSHOT common diff --git a/connector/pom.xml b/connector/pom.xml index d91b27d47..8b7b111de 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -6,7 +6,7 @@ org.geysermc geyser-parent - 1.3.0-SNAPSHOT + 1.4.0-SNAPSHOT connector @@ -21,7 +21,7 @@ org.geysermc common - 1.3.0-SNAPSHOT + 1.4.0-SNAPSHOT compile diff --git a/pom.xml b/pom.xml index 6146d9862..5b3e5a5f2 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 org.geysermc geyser-parent - 1.3.0-SNAPSHOT + 1.4.0-SNAPSHOT pom Geyser Allows for players from Minecraft Bedrock Edition to join Minecraft Java Edition servers. From 01822672a550ead357b95d3d6655de4e814c9906 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 8 Jun 2021 08:57:03 -0400 Subject: [PATCH 105/107] Remove debug elements --- connector/pom.xml | 3 --- .../geysermc/connector/network/session/GeyserSession.java | 7 ++++--- .../network/translators/world/block/BlockTranslator.java | 5 +---- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/connector/pom.xml b/connector/pom.xml index 8b7b111de..30fec659c 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -155,9 +155,6 @@ com.github.GeyserMC MCProtocolLib e316986 - - - compile 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 90b5e5929..73dad7220 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 @@ -58,14 +58,15 @@ import com.nukkitx.protocol.bedrock.data.command.CommandPermission; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.packet.*; -import com.nukkitx.protocol.bedrock.v431.Bedrock_v431; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.IntList; 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.*; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectIterator; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import lombok.AccessLevel; import lombok.Getter; import lombok.NonNull; @@ -806,7 +807,7 @@ public class GeyserSession implements CommandSender { @Override public void packetError(PacketErrorEvent event) { connector.getLogger().warning(LanguageUtils.getLocaleStringLog("geyser.network.downstream_error", event.getCause().getMessage())); - //if (connector.getConfig().isDebugMode()) //TODO don't leave this uncommented + if (connector.getConfig().isDebugMode()) event.getCause().printStackTrace(); event.setSuppress(true); } 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 43bd9d934..ed7818924 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 @@ -270,10 +270,7 @@ public abstract class BlockTranslator { NbtMap blockTag = buildBedrockState(entry.getValue()); int bedrockRuntimeId = blockStateOrderedMap.getOrDefault(blockTag, -1); if (bedrockRuntimeId == -1) { - //TODO REMOVE THIS COMMENT BEFORE RELEASE!!!! :) - //throw new RuntimeException("Unable to find " + javaId + " Bedrock runtime ID! Built compound tag: \n" + blockTag); - bedrockRuntimeId = 0; - GeyserConnector.getInstance().getLogger().warning("Unable to find " + javaId + " Bedrock runtime ID!"); + throw new RuntimeException("Unable to find " + javaId + " Bedrock runtime ID! Built compound tag: \n" + blockTag); } switch (javaId) { From 118f821281c303a80c00f035b8cbbfba6dae0953 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 8 Jun 2021 08:57:27 -0400 Subject: [PATCH 106/107] Update mappings --- 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 af2d3a85b..cc93558bd 160000 --- a/connector/src/main/resources/mappings +++ b/connector/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit af2d3a85b04517ba76e72d48eef5bb3b837ec5bb +Subproject commit cc93558bde00d6f081b8339ef53cf157d242d3f1 From 60e6f8c16268242e1d69885ecb5ccb137d2f4d44 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Tue, 8 Jun 2021 15:31:50 +0200 Subject: [PATCH 107/107] Catch AssertionError --- .../src/main/java/org/geysermc/connector/GeyserConnector.java | 2 +- 1 file changed, 1 insertion(+), 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 1d11c52d9..ac77388ff 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -236,7 +236,7 @@ public class GeyserConnector { if (build != null) { buildNumber = Integer.parseInt(build); } - } catch (Exception e) { + } catch (Throwable e) { logger.error("Failed to read git.properties", e); } newsHandler = new NewsHandler(branch, buildNumber);