From 8e9f862c88b76739733fb6ba6dcf199e2ef3a71d Mon Sep 17 00:00:00 2001 From: SupremeMortal <6178101+SupremeMortal@users.noreply.github.com> Date: Wed, 26 Aug 2020 22:51:11 +0100 Subject: [PATCH 001/205] Merge commands with the same parameters --- .../java/JavaDeclareCommandsTranslator.java | 214 +++++++++++------- 1 file changed, 129 insertions(+), 85 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareCommandsTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareCommandsTranslator.java index 8f524336..beed8232 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareCommandsTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareCommandsTranslator.java @@ -33,94 +33,57 @@ import com.nukkitx.protocol.bedrock.data.command.CommandEnumData; import com.nukkitx.protocol.bedrock.data.command.CommandParamData; import com.nukkitx.protocol.bedrock.data.command.CommandParamType; import com.nukkitx.protocol.bedrock.packet.AvailableCommandsPacket; +import it.unimi.dsi.fastutil.Hash; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap; import lombok.Getter; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; +import java.util.*; @Translator(packet = ServerDeclareCommandsPacket.class) public class JavaDeclareCommandsTranslator extends PacketTranslator { - @Override - public void translate(ServerDeclareCommandsPacket packet, GeyserSession session) { - // Don't send command suggestions if they are disabled - if (!session.getConnector().getConfig().isCommandSuggestions()) { - session.getConnector().getLogger().debug("Not sending command suggestions as they are disabled."); - return; + + private static final String[] ENUM_BOOLEAN = {"true", "false"}; + + private static final Hash.Strategy PARAM_STRATEGY = new Hash.Strategy() { + @Override + public int hashCode(CommandParamData[][] o) { + return Arrays.deepHashCode(o); } - List commandData = new ArrayList<>(); - Int2ObjectMap commands = new Int2ObjectOpenHashMap<>(); - Int2ObjectMap> commandArgs = new Int2ObjectOpenHashMap<>(); - // Get the first node, it should be a root node - CommandNode rootNode = packet.getNodes()[packet.getFirstNodeIndex()]; + @Override + public boolean equals(CommandParamData[][] a, CommandParamData[][] b) { + if (a == b) return true; + if (a == null || b == null) return false; + if (a.length != b.length) return false; + for (int i = 0; i < a.length; i++) { + CommandParamData[] a1 = a[i]; + CommandParamData[] b1 = b[i]; + if (a1.length != b1.length) return false; - // Loop through the root nodes to get all commands - for (int nodeIndex : rootNode.getChildIndices()) { - CommandNode node = packet.getNodes()[nodeIndex]; - - // Make sure we don't have duplicated commands (happens if there is more than 1 root node) - if (commands.containsKey(nodeIndex)) { continue; } - if (commands.containsValue(node.getName())) { continue; } - - // Get and update the commandArgs list with the found arguments - if (node.getChildIndices().length >= 1) { - for (int childIndex : node.getChildIndices()) { - commandArgs.putIfAbsent(nodeIndex, new ArrayList<>()); - commandArgs.get(nodeIndex).add(packet.getNodes()[childIndex]); + for (int i2 = 0; i2 < a1.length; i2++) { + if (!a1[i].equals(b1[i])) return false; } } - - // Insert the command name into the list - commands.put(nodeIndex, node.getName()); + return true; } - - // The command flags, not sure what these do apart from break things - List flags = new ArrayList<>(); - - // Loop through all the found commands - for (int commandID : commands.keySet()) { - String commandName = commands.get(commandID); - - // Create a basic alias - CommandEnumData aliases = new CommandEnumData( commandName + "Aliases", new String[] { commandName.toLowerCase() }, false); - - // Get and parse all params - CommandParamData[][] params = getParams(packet.getNodes()[commandID], packet.getNodes()); - - // Build the completed command and add it to the final list - CommandData data = new CommandData(commandName, session.getConnector().getCommandManager().getDescription(commandName), flags, (byte) 0, aliases, params); - commandData.add(data); - } - - // Add our commands to the AvailableCommandsPacket for the bedrock client - AvailableCommandsPacket availableCommandsPacket = new AvailableCommandsPacket(); - for (CommandData data : commandData) { - availableCommandsPacket.getCommands().add(data); - } - - GeyserConnector.getInstance().getLogger().debug("Sending command packet of " + commandData.size() + " commands"); - - // Finally, send the commands to the client - session.sendUpstreamPacket(availableCommandsPacket); - } + }; /** * Build the command parameter array for the given command * * @param commandNode The command to build the parameters for - * @param allNodes Every command node - * + * @param allNodes Every command node * @return An array of parameter option arrays */ - private CommandParamData[][] getParams(CommandNode commandNode, CommandNode[] allNodes) { + private static CommandParamData[][] getParams(CommandNode commandNode, CommandNode[] allNodes) { // Check if the command is an alias and redirect it if (commandNode.getRedirectIndex() != -1) { GeyserConnector.getInstance().getLogger().debug("Redirecting command " + commandNode.getName() + " to " + allNodes[commandNode.getRedirectIndex()].getName()); @@ -133,16 +96,8 @@ public class JavaDeclareCommandsTranslator extends PacketTranslator treeData = rootParam.getTree(); - CommandParamData[][] params = new CommandParamData[treeData.size()][]; - // Fill the nested params array - int i = 0; - for (CommandParamData[] tree : treeData) { - params[i] = tree; - i++; - } - - return params; + return treeData.toArray(new CommandParamData[0][]); } return new CommandParamData[0][0]; @@ -152,14 +107,17 @@ public class JavaDeclareCommandsTranslator extends PacketTranslator commandData = new ArrayList<>(); + IntSet commandNodes = new IntOpenHashSet(); + Set knownAliases = new HashSet<>(); + Map> commands = new Object2ObjectOpenCustomHashMap<>(PARAM_STRATEGY); + Int2ObjectMap> commandArgs = new Int2ObjectOpenHashMap<>(); + + // Get the first node, it should be a root node + CommandNode rootNode = nodes[packet.getFirstNodeIndex()]; + + // Loop through the root nodes to get all commands + for (int nodeIndex : rootNode.getChildIndices()) { + CommandNode node = nodes[nodeIndex]; + + // Make sure we don't have duplicated commands (happens if there is more than 1 root node) + if (!commandNodes.add(nodeIndex) || !knownAliases.add(node.getName().toLowerCase())) continue; + + // Get and update the commandArgs list with the found arguments + if (node.getChildIndices().length >= 1) { + for (int childIndex : node.getChildIndices()) { + commandArgs.putIfAbsent(nodeIndex, new ArrayList<>()); + commandArgs.get(nodeIndex).add(nodes[childIndex]); + } + } + + // Get and parse all params + CommandParamData[][] params = getParams(nodes[nodeIndex], nodes); + + // Insert the alias name into the command list + commands.computeIfAbsent(params, index -> new HashSet<>()).add(node.getName().toLowerCase()); + } + + // The command flags, not sure what these do apart from break things + List flags = new ArrayList<>(); + + // Loop through all the found commands + + for (Map.Entry> entry : commands.entrySet()) { + String commandName = entry.getValue().iterator().next(); // We know this has a value + + // Create a basic alias + CommandEnumData aliases = new CommandEnumData(commandName + "Aliases", entry.getValue().toArray(new String[0]), false); + + // Build the completed command and add it to the final list + CommandData data = new CommandData(commandName, session.getConnector().getCommandManager().getDescription(commandName), flags, (byte) 0, aliases, entry.getKey()); + commandData.add(data); + } + + // Add our commands to the AvailableCommandsPacket for the bedrock client + AvailableCommandsPacket availableCommandsPacket = new AvailableCommandsPacket(); + for (CommandData data : commandData) { + availableCommandsPacket.getCommands().add(data); + } + + GeyserConnector.getInstance().getLogger().debug("Sending command packet of " + commandData.size() + " commands"); + + // Finally, send the commands to the client + session.sendUpstreamPacket(availableCommandsPacket); + } + @Getter - private class ParamInfo { + private static class ParamInfo { private CommandNode paramNode; private CommandParamData paramData; private List children; @@ -259,8 +286,17 @@ public class JavaDeclareCommandsTranslator extends PacketTranslator Date: Fri, 28 Aug 2020 16:29:48 +0100 Subject: [PATCH 002/205] Fix reflections relocation (#1199) --- bootstrap/bungeecord/pom.xml | 2 +- bootstrap/spigot/pom.xml | 2 +- bootstrap/sponge/pom.xml | 2 +- bootstrap/velocity/pom.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bootstrap/bungeecord/pom.xml b/bootstrap/bungeecord/pom.xml index 8497b968..682690a2 100644 --- a/bootstrap/bungeecord/pom.xml +++ b/bootstrap/bungeecord/pom.xml @@ -66,7 +66,7 @@ org.geysermc.platform.bungeecord.shaded.netty - org.reflections.reflections + org.reflections org.geysermc.platform.bungeecord.shaded.reflections diff --git a/bootstrap/spigot/pom.xml b/bootstrap/spigot/pom.xml index 422b2769..8b3ad742 100644 --- a/bootstrap/spigot/pom.xml +++ b/bootstrap/spigot/pom.xml @@ -76,7 +76,7 @@ org.geysermc.platform.spigot.shaded.jackson - org.reflections.reflections + org.reflections org.geysermc.platform.spigot.shaded.reflections diff --git a/bootstrap/sponge/pom.xml b/bootstrap/sponge/pom.xml index f3c89808..c21a3a53 100644 --- a/bootstrap/sponge/pom.xml +++ b/bootstrap/sponge/pom.xml @@ -70,7 +70,7 @@ org.geysermc.platform.sponge.shaded.fastutil - org.reflections.reflections + org.reflections org.geysermc.platform.sponge.shaded.reflections diff --git a/bootstrap/velocity/pom.xml b/bootstrap/velocity/pom.xml index b08e5cbc..0ebb70ca 100644 --- a/bootstrap/velocity/pom.xml +++ b/bootstrap/velocity/pom.xml @@ -62,7 +62,7 @@ org.geysermc.platform.velocity.shaded.fastutil - org.reflections.reflections + org.reflections org.geysermc.platform.velocity.shaded.reflections From 1c849938539ed919f184d4243fd674728ca65127 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Fri, 28 Aug 2020 17:47:52 +0200 Subject: [PATCH 003/205] Scoreboard improvements (#1166) * Added a way to check if debug logging is enabled * Improved scoreboard performance * Include Teams in pps and return pending pps instead when higher then pps Some servers have a huge amount of score packets when the player logs in, but before this commit, only after the first high pps (packets per second) the ScoreboardUpdater will be used (after pending packets per second have been moved to packets per second). But this commit fixes that the ScoreboardUpdater can be used on the second that the pps is getting high. * Fixed team pre + suffix "null" issue and added threshold config option Fixed team pre + suffix "null" issue. When the prefix and/or suffix of a Team is null, "null" will be returned instead of null (Due to the way that MCProtocolLib is made and designed). This is fixed by simply checking if the prefix and/or suffix equal "null" and if that is the case, replace it with "". Added threshold option. Gave the person who is running Geyser an option to specify the first Scoreboard packets per second threshold to further improve performance by lowering the setting or decrease performance by relaxing the setting a bit. The value can't be higher then 250 (the second threshold), because it'll always choose the lowest threshold. * Forgot to bump config version * Small changes * Reverted version bump, changed Sponge config, changed FloodgateKeyLoader Reverted version bump Camotoy said that you only need to bump the config version if the change is breaking, the config version bump has been reverted. Changed Sponge config The Sponge config has been modified to look like the other platform configurations. Changed FloodgateKeyLoader * Changed default-locale and (remote) address as requested by Camotoy * Reduce bandwidth and a few final tweaks * Made the scoreboard-packet-threshold a bit higher due to improvements --- .../bungeecord/GeyserBungeeConfiguration.java | 17 +- .../bungeecord/GeyserBungeeLogger.java | 20 +- .../bungeecord/GeyserBungeePlugin.java | 7 +- .../spigot/GeyserSpigotConfiguration.java | 18 +- .../platform/spigot/GeyserSpigotLogger.java | 17 +- .../sponge/GeyserSpongeConfiguration.java | 263 +----------------- .../platform/sponge/GeyserSpongeLogger.java | 17 +- .../platform/sponge/GeyserSpongePlugin.java | 16 +- .../GeyserStandaloneConfiguration.java | 11 +- .../standalone/GeyserStandaloneLogger.java | 12 +- .../velocity/GeyserVelocityConfiguration.java | 19 +- .../velocity/GeyserVelocityLogger.java | 17 +- .../connector/FloodgateKeyLoader.java | 8 +- .../org/geysermc/connector/GeyserLogger.java | 5 + .../configuration/GeyserConfiguration.java | 5 +- .../GeyserJacksonConfiguration.java | 60 ++-- .../connector/entity/PlayerEntity.java | 17 +- .../network/session/GeyserSession.java | 6 +- .../network/session/UpstreamSession.java | 14 +- .../network/session/cache/WorldCache.java | 23 +- .../JavaScoreboardObjectiveTranslator.java | 10 +- .../java/scoreboard/JavaTeamTranslator.java | 60 ++-- .../scoreboard/JavaUpdateScoreTranslator.java | 60 ++-- .../connector/scoreboard/Objective.java | 81 +++--- .../geysermc/connector/scoreboard/Score.java | 39 ++- .../connector/scoreboard/Scoreboard.java | 178 ++++++------ .../scoreboard/ScoreboardUpdater.java | 121 ++++++++ .../geysermc/connector/scoreboard/Team.java | 38 ++- connector/src/main/resources/config.yml | 6 + 29 files changed, 549 insertions(+), 616 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/scoreboard/ScoreboardUpdater.java 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 d9b86a2e..00b091d1 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 @@ -29,27 +29,22 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import lombok.Getter; import net.md_5.bungee.api.plugin.Plugin; -import net.md_5.bungee.config.Configuration; import org.geysermc.connector.FloodgateKeyLoader; import org.geysermc.connector.configuration.GeyserJacksonConfiguration; import java.nio.file.Path; -import java.nio.file.Paths; @Getter @JsonIgnoreProperties(ignoreUnknown = true) -public class GeyserBungeeConfiguration extends GeyserJacksonConfiguration { - +public final class GeyserBungeeConfiguration extends GeyserJacksonConfiguration { @JsonIgnore - private Path floodgateKey; + private Path floodgateKeyPath; - public void loadFloodgate(GeyserBungeePlugin plugin, Configuration configuration) { + public void loadFloodgate(GeyserBungeePlugin plugin) { Plugin floodgate = plugin.getProxy().getPluginManager().getPlugin("floodgate-bungee"); - 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); - } + Path geyserDataFolder = plugin.getDataFolder().toPath(); + Path floodgateDataFolder = floodgate != null ? floodgate.getDataFolder().toPath() : null; - @Override - public Path getFloodgateKeyFile() { - return floodgateKey; + floodgateKeyPath = FloodgateKeyLoader.getKeyPath(this, floodgate, floodgateDataFolder, geyserDataFolder, plugin.getGeyserLogger()); } } diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeLogger.java b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeLogger.java index cd07b333..e40f404c 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeLogger.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeLogger.java @@ -25,19 +25,21 @@ package org.geysermc.platform.bungeecord; +import lombok.Getter; +import lombok.Setter; import org.geysermc.connector.GeyserLogger; import java.util.logging.Level; import java.util.logging.Logger; public class GeyserBungeeLogger implements GeyserLogger { + private final Logger logger; + @Getter @Setter + private boolean debug; - private Logger logger; - private boolean debugMode; - - public GeyserBungeeLogger(Logger logger, boolean debugMode) { + public GeyserBungeeLogger(Logger logger, boolean debug) { this.logger = logger; - this.debugMode = debugMode; + this.debug = debug; } @Override @@ -72,12 +74,8 @@ public class GeyserBungeeLogger implements GeyserLogger { @Override public void debug(String message) { - if (debugMode) + if (debug) { info(message); - } - - @Override - public void setDebug(boolean debug) { - debugMode = debug; + } } } 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 e80207d3..abb9789e 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java @@ -27,9 +27,6 @@ package org.geysermc.platform.bungeecord; import net.md_5.bungee.api.config.ListenerInfo; import net.md_5.bungee.api.plugin.Plugin; -import net.md_5.bungee.config.Configuration; -import net.md_5.bungee.config.ConfigurationProvider; -import net.md_5.bungee.config.YamlConfiguration; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.bootstrap.GeyserBootstrap; import org.geysermc.connector.command.CommandManager; @@ -64,13 +61,11 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { if (!getDataFolder().exists()) getDataFolder().mkdir(); - Configuration configuration = null; try { if (!getDataFolder().exists()) getDataFolder().mkdir(); File configFile = FileUtils.fileOrCopiedFromResource(new File(getDataFolder(), "config.yml"), "config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString())); this.geyserConfig = FileUtils.loadConfig(configFile, GeyserBungeeConfiguration.class); - configuration = ConfigurationProvider.getProvider(YamlConfiguration.class).load(new File(getDataFolder(), "config.yml")); } catch (IOException ex) { getLogger().log(Level.WARNING, LanguageUtils.getLocaleStringLog("geyser.config.failed"), ex); ex.printStackTrace(); @@ -108,7 +103,7 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { geyserConfig.getRemote().setAuthType("floodgate"); } - geyserConfig.loadFloodgate(this, configuration); + geyserConfig.loadFloodgate(this); this.connector = GeyserConnector.start(PlatformType.BUNGEECORD, this); 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 380f7037..5c48efe8 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 @@ -27,7 +27,6 @@ package org.geysermc.platform.spigot; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; import org.bukkit.Bukkit; import org.bukkit.plugin.Plugin; @@ -35,26 +34,19 @@ import org.geysermc.connector.FloodgateKeyLoader; import org.geysermc.connector.configuration.GeyserJacksonConfiguration; import java.nio.file.Path; -import java.nio.file.Paths; @Getter @JsonIgnoreProperties(ignoreUnknown = true) -public class GeyserSpigotConfiguration extends GeyserJacksonConfiguration { - - @JsonProperty("floodgate-key-file") - private String floodgateKeyFile; - +public final class GeyserSpigotConfiguration extends GeyserJacksonConfiguration { @JsonIgnore - private Path floodgateKey; + private Path floodgateKeyPath; public void loadFloodgate(GeyserSpigotPlugin plugin) { Plugin floodgate = Bukkit.getPluginManager().getPlugin("floodgate-bukkit"); - 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); - } + Path geyserDataFolder = plugin.getDataFolder().toPath(); + Path floodgateDataFolder = floodgate != null ? floodgate.getDataFolder().toPath() : null; - @Override - public Path getFloodgateKeyFile() { - return floodgateKey; + floodgateKeyPath = FloodgateKeyLoader.getKeyPath(this, floodgate, floodgateDataFolder, geyserDataFolder, plugin.getGeyserLogger()); } @Override diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotLogger.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotLogger.java index 252d6bbe..b462f1f1 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotLogger.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotLogger.java @@ -26,7 +26,8 @@ package org.geysermc.platform.spigot; import lombok.AllArgsConstructor; - +import lombok.Getter; +import lombok.Setter; import org.geysermc.connector.GeyserLogger; import java.util.logging.Level; @@ -34,9 +35,9 @@ import java.util.logging.Logger; @AllArgsConstructor public class GeyserSpigotLogger implements GeyserLogger { - - private Logger logger; - private boolean debugMode; + private final Logger logger; + @Getter @Setter + private boolean debug; @Override public void severe(String message) { @@ -70,12 +71,8 @@ public class GeyserSpigotLogger implements GeyserLogger { @Override public void debug(String message) { - if (debugMode) + if (debug) { info(message); - } - - @Override - public void setDebug(boolean debug) { - debugMode = debug; + } } } diff --git a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeConfiguration.java b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeConfiguration.java index ba416110..2d5eefeb 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeConfiguration.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeConfiguration.java @@ -25,268 +25,13 @@ package org.geysermc.platform.sponge; -import lombok.AllArgsConstructor; -import ninja.leaping.configurate.ConfigurationNode; -import org.geysermc.connector.configuration.GeyserConfiguration; +import org.geysermc.connector.configuration.GeyserJacksonConfiguration; -import java.io.File; import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.*; - -public class GeyserSpongeConfiguration implements GeyserConfiguration { - - private File dataFolder; - private ConfigurationNode node; - - /** - * If the config was originally 'auto' before the values changed - */ - private boolean autoconfiguredRemote = false; - - private SpongeBedrockConfiguration bedrockConfig; - private SpongeRemoteConfiguration remoteConfig; - private SpongeMetricsInfo metricsInfo; - - private Map userAuthInfo = new HashMap<>(); - - public GeyserSpongeConfiguration(File dataFolder, ConfigurationNode node) { - this.dataFolder = dataFolder; - this.node = node; - - this.bedrockConfig = new SpongeBedrockConfiguration(node.getNode("bedrock")); - this.remoteConfig = new SpongeRemoteConfiguration(node.getNode("remote")); - this.metricsInfo = new SpongeMetricsInfo(); - - if (node.getNode("userAuths").getValue() == null) - return; - - List userAuths = new ArrayList(((LinkedHashMap)node.getNode("userAuths").getValue()).keySet()); - for (String key : userAuths) { - userAuthInfo.put(key, new SpongeUserAuthenticationInfo(key)); - } - } - - public void setAutoconfiguredRemote(boolean autoconfiguredRemote) { - this.autoconfiguredRemote = autoconfiguredRemote; - } +public final class GeyserSpongeConfiguration extends GeyserJacksonConfiguration { @Override - public SpongeBedrockConfiguration getBedrock() { - return bedrockConfig; - } - - @Override - public SpongeRemoteConfiguration getRemote() { - return remoteConfig; - } - - @Override - public Map getUserAuths() { - return userAuthInfo; - } - - @Override - public boolean isCommandSuggestions() { - return node.getNode("command-suggestions").getBoolean(true); - } - - @Override - public boolean isPassthroughMotd() { - return node.getNode("passthrough-motd").getBoolean(false); - } - - @Override - public boolean isPassthroughProtocolName() { - return node.getNode("passthrough-protocol-name").getBoolean(false); - } - - @Override - public boolean isPassthroughPlayerCounts() { - return node.getNode("passthrough-player-counts").getBoolean(false); - } - - @Override - public boolean isLegacyPingPassthrough() { - return node.getNode("legacy-ping-passthrough").getBoolean(false); - } - - @Override - public int getPingPassthroughInterval() { - return node.getNode("ping-passthrough-interval").getInt(3); - } - - @Override - public int getMaxPlayers() { - return node.getNode("max-players").getInt(100); - } - - @Override - public boolean isDebugMode() { - return node.getNode("debug-mode").getBoolean(false); - } - - @Override - public int getGeneralThreadPool() { - return node.getNode("genereal-thread-pool").getInt(32); - } - - @Override - public boolean isAllowThirdPartyCapes() { - return node.getNode("allow-third-party-capes").getBoolean(true); - } - - @Override - public boolean isAllowThirdPartyEars() { - return node.getNode("allow-third-party-ears").getBoolean(false); - } - - @Override - public boolean isShowCooldown() { - return node.getNode("show-cooldown").getBoolean(true); - } - - @Override - public String getDefaultLocale() { - return node.getNode("default-locale").getString("en_us"); - } - - @Override - public Path getFloodgateKeyFile() { - return Paths.get(dataFolder.toString(), node.getNode("floodgate-key-file").getString("public-key.pem")); - } - - @Override - public boolean isCacheChunks() { - return node.getNode("cache-chunks").getBoolean(false); - } - - @Override - public int getCacheImages() { - return node.getNode("cache-skins").getInt(0); - } - - @Override - public boolean isAboveBedrockNetherBuilding() { - return node.getNode("above-bedrock-nether-building").getBoolean(false); - } - - @Override - public SpongeMetricsInfo getMetrics() { - return metricsInfo; - } - - @AllArgsConstructor - public class SpongeBedrockConfiguration implements IBedrockConfiguration { - - private ConfigurationNode node; - - @Override - public String getAddress() { - return node.getNode("address").getString("0.0.0.0"); - } - - @Override - public int getPort() { - return node.getNode("port").getInt(19132); - } - - @Override - public boolean isCloneRemotePort() { - return node.getNode("clone-remote-port").getBoolean(false); - } - - @Override - public String getMotd1() { - return node.getNode("motd1").getString("GeyserMC"); - } - - @Override - public String getMotd2() { - return node.getNode("motd2").getString("GeyserMC"); - } - - @Override - public String getServerName() { - return node.getNode("server-name").getString("Geyser"); - } - } - - @AllArgsConstructor - public class SpongeRemoteConfiguration implements IRemoteConfiguration { - - private ConfigurationNode node; - - @Override - public String getAddress() { - return node.getNode("address").getString("127.0.0.1"); - } - - @Override - public void setAddress(String address) { - node.getNode("address").setValue(address); - } - - @Override - public int getPort() { - return node.getNode("port").getInt(25565); - } - - @Override - public void setPort(int port) { - node.getNode("port").setValue(port); - } - - @Override - public String getAuthType() { - return node.getNode("auth-type").getString("online"); - } - } - - public class SpongeUserAuthenticationInfo implements IUserAuthenticationInfo { - - private String key; - - public SpongeUserAuthenticationInfo(String key) { - this.key = key; - } - - @Override - public String getEmail() { - return node.getNode("userAuths").getNode(key).getNode("email").getString(); - } - - @Override - public String getPassword() { - return node.getNode("userAuths").getNode(key).getNode("password").getString(); - } - } - - public class SpongeMetricsInfo implements IMetricsInfo { - - @Override - public boolean isEnabled() { - return node.getNode("metrics").getNode("enabled").getBoolean(true); - } - - @Override - public String getUniqueId() { - return node.getNode("metrics").getNode("uuid").getString("generateduuid"); - } - } - - @Override - public boolean isEnableProxyConnections() { - return node.getNode("enable-proxy-connections").getBoolean(false); - } - - @Override - public int getMtu() { - return node.getNode("mtu").getInt(1400); - } - - @Override - public int getConfigVersion() { - return node.getNode("config-version").getInt(0); + public Path getFloodgateKeyPath() { + return null; //floodgate isn't available for Sponge } } diff --git a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeLogger.java b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeLogger.java index fb7cb54b..bdbd2531 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeLogger.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeLogger.java @@ -26,15 +26,16 @@ package org.geysermc.platform.sponge; import lombok.AllArgsConstructor; - +import lombok.Getter; +import lombok.Setter; import org.geysermc.connector.GeyserLogger; import org.slf4j.Logger; @AllArgsConstructor public class GeyserSpongeLogger implements GeyserLogger { - - private Logger logger; - private boolean debugMode; + private final Logger logger; + @Getter @Setter + private boolean debug; @Override public void severe(String message) { @@ -68,12 +69,8 @@ public class GeyserSpongeLogger implements GeyserLogger { @Override public void debug(String message) { - if (debugMode) + if (debug) { info(message); - } - - @Override - public void setDebug(boolean debugMode) { - this.debugMode = debugMode; + } } } diff --git a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java index 0f1b7253..106d2b15 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java @@ -26,9 +26,6 @@ package org.geysermc.platform.sponge; import com.google.inject.Inject; -import ninja.leaping.configurate.ConfigurationNode; -import ninja.leaping.configurate.loader.ConfigurationLoader; -import ninja.leaping.configurate.yaml.YAMLConfigurationLoader; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.bootstrap.GeyserBootstrap; import org.geysermc.connector.command.CommandManager; @@ -85,20 +82,14 @@ public class GeyserSpongePlugin implements GeyserBootstrap { ex.printStackTrace(); } - ConfigurationLoader loader = YAMLConfigurationLoader.builder().setPath(configFile.toPath()).build(); - ConfigurationNode config; try { - config = loader.load(); - this.geyserConfig = new GeyserSpongeConfiguration(configDir, config); + this.geyserConfig = FileUtils.loadConfig(configFile, GeyserSpongeConfiguration.class); } catch (IOException ex) { logger.warn(LanguageUtils.getLocaleStringLog("geyser.config.failed")); ex.printStackTrace(); return; } - ConfigurationNode serverIP = config.getNode("remote").getNode("address"); - ConfigurationNode serverPort = config.getNode("remote").getNode("port"); - if (Sponge.getServer().getBoundAddress().isPresent()) { InetSocketAddress javaAddr = Sponge.getServer().getBoundAddress().get(); @@ -106,13 +97,12 @@ public class GeyserSpongePlugin implements GeyserBootstrap { // By default this should be 127.0.0.1 but may need to be changed in some circumstances if (this.geyserConfig.getRemote().getAddress().equalsIgnoreCase("auto")) { this.geyserConfig.setAutoconfiguredRemote(true); - serverPort.setValue(javaAddr.getPort()); + geyserConfig.getRemote().setPort(javaAddr.getPort()); } } - ConfigurationNode bedrockPort = config.getNode("bedrock").getNode("port"); if (geyserConfig.getBedrock().isCloneRemotePort()){ - bedrockPort.setValue(serverPort.getValue()); + geyserConfig.getBedrock().setPort(geyserConfig.getRemote().getPort()); } this.geyserLogger = new GeyserSpongeLogger(logger, geyserConfig.isDebugMode()); diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneConfiguration.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneConfiguration.java index 29e18d08..5707863c 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneConfiguration.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneConfiguration.java @@ -26,7 +26,6 @@ package org.geysermc.platform.standalone; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; import org.geysermc.connector.configuration.GeyserJacksonConfiguration; @@ -35,13 +34,9 @@ import java.nio.file.Paths; @Getter @JsonIgnoreProperties(ignoreUnknown = true) -public class GeyserStandaloneConfiguration extends GeyserJacksonConfiguration { - - @JsonProperty("floodgate-key-file") - private String floodgateKeyFile; - +public final class GeyserStandaloneConfiguration extends GeyserJacksonConfiguration { @Override - public Path getFloodgateKeyFile() { - return Paths.get(floodgateKeyFile); + public Path getFloodgateKeyPath() { + return Paths.get(getFloodgateKeyFile()); } } diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneLogger.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneLogger.java index 0d9ced95..f0f7156f 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneLogger.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneLogger.java @@ -26,18 +26,16 @@ package org.geysermc.platform.standalone; import lombok.extern.log4j.Log4j2; - import net.minecrell.terminalconsole.SimpleTerminalConsole; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.config.Configurator; -import org.geysermc.connector.common.ChatColor; import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.GeyserLogger; import org.geysermc.connector.command.CommandSender; +import org.geysermc.connector.common.ChatColor; @Log4j2 -public class GeyserStandaloneLogger extends SimpleTerminalConsole implements org.geysermc.connector.GeyserLogger, CommandSender { - +public class GeyserStandaloneLogger extends SimpleTerminalConsole implements GeyserLogger, CommandSender { private boolean colored = true; @Override @@ -99,10 +97,6 @@ public class GeyserStandaloneLogger extends SimpleTerminalConsole implements org Configurator.setLevel(log.getName(), debug ? Level.DEBUG : Level.INFO); } - /** - * Used for setting debug mode in GUI mode - * @return if debug is enabled - */ public boolean isDebug() { return log.isDebugEnabled(); } diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityConfiguration.java b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityConfiguration.java index 07580443..7dc6746b 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityConfiguration.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityConfiguration.java @@ -27,7 +27,6 @@ package org.geysermc.platform.velocity; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; import com.velocitypowered.api.plugin.PluginContainer; import com.velocitypowered.api.proxy.ProxyServer; import lombok.Getter; @@ -37,25 +36,15 @@ import org.geysermc.connector.configuration.GeyserJacksonConfiguration; import java.io.File; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.Optional; @Getter @JsonIgnoreProperties(ignoreUnknown = true) -public class GeyserVelocityConfiguration extends GeyserJacksonConfiguration { - - @JsonProperty("floodgate-key-file") - private String floodgateKeyFile; - +public final class GeyserVelocityConfiguration extends GeyserJacksonConfiguration { @JsonIgnore - private Path floodgateKey; - - @Override - public Path getFloodgateKeyFile() { - return floodgateKey; - } + private Path floodgateKeyPath; public void loadFloodgate(GeyserVelocityPlugin plugin, ProxyServer proxyServer, File dataFolder) { - Optional floodgate = proxyServer.getPluginManager().getPlugin("floodgate"); - floodgate.ifPresent(it -> floodgateKey = FloodgateKeyLoader.getKey(plugin.getGeyserLogger(), this, Paths.get(dataFolder.toString(), floodgateKeyFile.isEmpty() ? floodgateKeyFile : "public-key.pem"), it, Paths.get("plugins/floodgate/"))); + PluginContainer floodgate = proxyServer.getPluginManager().getPlugin("floodgate").orElse(null); + floodgateKeyPath = FloodgateKeyLoader.getKeyPath(this, floodgate, Paths.get("plugins/floodgate/"), dataFolder.toPath(), plugin.getGeyserLogger()); } } diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityLogger.java b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityLogger.java index a935d786..cb98411c 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityLogger.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityLogger.java @@ -26,15 +26,16 @@ package org.geysermc.platform.velocity; import lombok.AllArgsConstructor; - +import lombok.Getter; +import lombok.Setter; import org.geysermc.connector.GeyserLogger; import org.slf4j.Logger; @AllArgsConstructor public class GeyserVelocityLogger implements GeyserLogger { - - private Logger logger; - private boolean debugMode; + private final Logger logger; + @Getter @Setter + private boolean debug; @Override public void severe(String message) { @@ -68,12 +69,8 @@ public class GeyserVelocityLogger implements GeyserLogger { @Override public void debug(String message) { - if (debugMode) + if (debug) { info(message); - } - - @Override - public void setDebug(boolean debugMode) { - this.debugMode = debugMode; + } } } \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/FloodgateKeyLoader.java b/connector/src/main/java/org/geysermc/connector/FloodgateKeyLoader.java index a30a5f4d..ec5dd349 100644 --- a/connector/src/main/java/org/geysermc/connector/FloodgateKeyLoader.java +++ b/connector/src/main/java/org/geysermc/connector/FloodgateKeyLoader.java @@ -25,17 +25,19 @@ package org.geysermc.connector; +import org.geysermc.connector.configuration.GeyserJacksonConfiguration; import org.geysermc.connector.utils.LanguageUtils; -import org.geysermc.connector.configuration.GeyserConfiguration; import java.nio.file.Files; import java.nio.file.Path; public class FloodgateKeyLoader { - public static Path getKey(GeyserLogger logger, GeyserConfiguration config, Path floodgateKey, Object floodgate, Path floodgateFolder) { + public static Path getKeyPath(GeyserJacksonConfiguration config, Object floodgate, Path floodgateDataFolder, Path geyserDataFolder, GeyserLogger logger) { + Path floodgateKey = geyserDataFolder.resolve(config.getFloodgateKeyFile()); + if (!Files.exists(floodgateKey) && config.getRemote().getAuthType().equals("floodgate")) { if (floodgate != null) { - Path autoKey = floodgateFolder.resolve("public-key.pem"); + Path autoKey = floodgateDataFolder.resolve("public-key.pem"); if (Files.exists(autoKey)) { logger.info(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.auto_loaded")); floodgateKey = autoKey; diff --git a/connector/src/main/java/org/geysermc/connector/GeyserLogger.java b/connector/src/main/java/org/geysermc/connector/GeyserLogger.java index 0ba2a689..138285eb 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserLogger.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserLogger.java @@ -84,4 +84,9 @@ public interface GeyserLogger { * @param debug if the logger should print debug messages */ void setDebug(boolean debug); + + /** + * If debug is enabled for this logger + */ + boolean isDebug(); } 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 5803ff13..2d76e574 100644 --- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java +++ b/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java @@ -27,7 +27,6 @@ package org.geysermc.connector.configuration; import com.fasterxml.jackson.annotation.JsonIgnore; import org.geysermc.connector.GeyserLogger; - import org.geysermc.connector.utils.LanguageUtils; import java.nio.file.Path; @@ -74,7 +73,7 @@ public interface GeyserConfiguration { String getDefaultLocale(); - Path getFloodgateKeyFile(); + Path getFloodgateKeyPath(); boolean isAboveBedrockNetherBuilding(); @@ -125,6 +124,8 @@ public interface GeyserConfiguration { String getUniqueId(); } + int getScoreboardPacketThreshold(); + // if u have offline mode enabled pls be safe boolean isEnableProxyConnections(); 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 378eba68..6748ef7e 100644 --- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java +++ b/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java @@ -49,76 +49,75 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration private RemoteConfiguration remote; @JsonProperty("floodgate-key-file") - private String floodgateKeyFile; + private String floodgateKeyFile = "public-key.pem"; - public abstract Path getFloodgateKeyFile(); + public abstract Path getFloodgateKeyPath(); private Map userAuths; @JsonProperty("command-suggestions") - private boolean commandSuggestions; + private boolean commandSuggestions = true; @JsonProperty("passthrough-motd") - private boolean isPassthroughMotd; + private boolean isPassthroughMotd = false; @JsonProperty("passthrough-player-counts") - private boolean isPassthroughPlayerCounts; + private boolean isPassthroughPlayerCounts = false; @JsonProperty("passthrough-protocol-name") - private boolean isPassthroughProtocolName; + private boolean isPassthroughProtocolName = false; @JsonProperty("legacy-ping-passthrough") - private boolean isLegacyPingPassthrough; + private boolean isLegacyPingPassthrough = false; @JsonProperty("ping-passthrough-interval") - private int pingPassthroughInterval; + private int pingPassthroughInterval = 3; @JsonProperty("max-players") - private int maxPlayers; + private int maxPlayers = 100; @JsonProperty("debug-mode") - private boolean debugMode; + private boolean debugMode = false; @JsonProperty("general-thread-pool") - private int generalThreadPool; + private int generalThreadPool = 32; @JsonProperty("allow-third-party-capes") - private boolean allowThirdPartyCapes; + private boolean allowThirdPartyCapes = true; @JsonProperty("show-cooldown") private boolean showCooldown = true; @JsonProperty("allow-third-party-ears") - private boolean allowThirdPartyEars; + private boolean allowThirdPartyEars = false; @JsonProperty("default-locale") - private String defaultLocale; + private String defaultLocale = null; // is null by default so system language takes priority @JsonProperty("cache-chunks") - private boolean cacheChunks; + private boolean cacheChunks = false; @JsonProperty("cache-images") private int cacheImages = 0; @JsonProperty("above-bedrock-nether-building") - private boolean aboveBedrockNetherBuilding; + private boolean aboveBedrockNetherBuilding = false; private MetricsInfo metrics; @Getter public static class BedrockConfiguration implements IBedrockConfiguration { - @AsteriskSerializer.Asterisk(sensitive = true) - private String address; + private String address = "0.0.0.0"; @Setter - private int port; + private int port = 19132; @JsonProperty("clone-remote-port") - private boolean cloneRemotePort; + private boolean cloneRemotePort = false; - private String motd1; - private String motd2; + private String motd1 = "GeyserMC"; + private String motd2 = "Geyser"; @JsonProperty("server-name") private String serverName = GeyserConnector.NAME; @@ -126,17 +125,16 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration @Getter public static class RemoteConfiguration implements IRemoteConfiguration { - @Setter @AsteriskSerializer.Asterisk(sensitive = true) - private String address; + private String address = "auto"; @Setter - private int port; + private int port = 25565; @Setter @JsonProperty("auth-type") - private String authType; + private String authType = "online"; } @Getter @@ -150,13 +148,15 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration @Getter public static class MetricsInfo implements IMetricsInfo { - - private boolean enabled; + private boolean enabled = true; @JsonProperty("uuid") - private String uniqueId; + private String uniqueId = "generateuuid"; } + @JsonProperty("scoreboard-packet-threshold") + private int scoreboardPacketThreshold = 10; + @JsonProperty("enable-proxy-connections") private boolean enableProxyConnections = false; @@ -164,5 +164,5 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration private int mtu = 1400; @JsonProperty("config-version") - private int configVersion; + private int configVersion = 0; } diff --git a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java index 52b27351..da75812f 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java @@ -30,13 +30,15 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadat import com.github.steveice10.mc.protocol.data.message.TextMessage; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.AdventureSetting; import com.nukkitx.protocol.bedrock.data.AttributeData; import com.nukkitx.protocol.bedrock.data.PlayerPermission; import com.nukkitx.protocol.bedrock.data.command.CommandPermission; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityLinkData; -import com.nukkitx.protocol.bedrock.packet.*; +import com.nukkitx.protocol.bedrock.packet.AddPlayerPacket; +import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket; +import com.nukkitx.protocol.bedrock.packet.SetEntityLinkPacket; +import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket; import lombok.Getter; import lombok.Setter; import org.geysermc.connector.entity.attribute.Attribute; @@ -48,7 +50,10 @@ import org.geysermc.connector.scoreboard.Team; import org.geysermc.connector.utils.AttributeUtils; import org.geysermc.connector.utils.MessageUtils; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; import java.util.concurrent.TimeUnit; @Getter @Setter @@ -209,11 +214,6 @@ public class PlayerEntity extends LivingEntity { super.updateBedrockMetadata(entityMetadata, session); if (entityMetadata.getId() == 2) { - // System.out.println(session.getScoreboardCache().getScoreboard().getObjectives().keySet()); - for (Team team : session.getWorldCache().getScoreboard().getTeams().values()) { - // session.getConnector().getLogger().info("team name " + team.getName()); - // session.getConnector().getLogger().info("team entities " + team.getEntities()); - } String username = this.username; TextMessage name = (TextMessage) entityMetadata.getValue(); if (name != null) { @@ -221,7 +221,6 @@ public class PlayerEntity extends LivingEntity { } Team team = session.getWorldCache().getScoreboard().getTeamFor(username); if (team != null) { - // session.getConnector().getLogger().info("team name es " + team.getName() + " with prefix " + team.getPrefix() + " and suffix " + team.getSuffix()); metadata.put(EntityData.NAMETAG, team.getPrefix() + MessageUtils.toChatColor(team.getColor()) + username + team.getSuffix()); } } 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 b208aaf3..3b27fc6a 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 @@ -346,7 +346,7 @@ public class GeyserSession implements CommandSender { PublicKey key = null; try { key = EncryptionUtil.getKeyFromFile( - connector.getConfig().getFloodgateKeyFile(), + connector.getConfig().getFloodgateKeyPath(), PublicKey.class ); } catch (IOException | InvalidKeySpecException | NoSuchAlgorithmException e) { @@ -626,7 +626,7 @@ public class GeyserSession implements CommandSender { * @param packet the bedrock packet from the NukkitX protocol lib */ public void sendUpstreamPacket(BedrockPacket packet) { - if (upstream != null && !upstream.isClosed()) { + if (upstream != null) { upstream.sendPacket(packet); } else { connector.getLogger().debug("Tried to send upstream packet " + packet.getClass().getSimpleName() + " but the session was null"); @@ -639,7 +639,7 @@ public class GeyserSession implements CommandSender { * @param packet the bedrock packet from the NukkitX protocol lib */ public void sendUpstreamPacketImmediately(BedrockPacket packet) { - if (upstream != null && !upstream.isClosed()) { + if (upstream != null) { upstream.sendPacketImmediately(packet); } else { connector.getLogger().debug("Tried to send upstream packet " + packet.getClass().getSimpleName() + " immediately but the session was null"); diff --git a/connector/src/main/java/org/geysermc/connector/network/session/UpstreamSession.java b/connector/src/main/java/org/geysermc/connector/network/session/UpstreamSession.java index 09870eef..393ebfa8 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/UpstreamSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/UpstreamSession.java @@ -41,17 +41,15 @@ public class UpstreamSession { private boolean initialized = false; public void sendPacket(@NonNull BedrockPacket packet) { - if (isClosed()) - return; - - session.sendPacket(packet); + if (!isClosed()) { + session.sendPacket(packet); + } } public void sendPacketImmediately(@NonNull BedrockPacket packet) { - if (isClosed()) - return; - - session.sendPacketImmediately(packet); + if (!isClosed()) { + session.sendPacketImmediately(packet); + } } public void disconnect(String reason) { diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/WorldCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/WorldCache.java index 310e5f9d..ce49d2a0 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/WorldCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/WorldCache.java @@ -31,37 +31,40 @@ import lombok.Setter; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.scoreboard.Objective; import org.geysermc.connector.scoreboard.Scoreboard; - -import java.util.Collection; +import org.geysermc.connector.scoreboard.ScoreboardUpdater; @Getter public class WorldCache { - - private GeyserSession session; - + private final GeyserSession session; @Setter private Difficulty difficulty = Difficulty.EASY; - private boolean showCoordinates = true; private Scoreboard scoreboard; + private final ScoreboardUpdater scoreboardUpdater; public WorldCache(GeyserSession session) { this.session = session; this.scoreboard = new Scoreboard(session); + scoreboardUpdater = new ScoreboardUpdater(this); + scoreboardUpdater.start(); } public void removeScoreboard() { if (scoreboard != null) { - Collection objectives = scoreboard.getObjectives().values(); - scoreboard = new Scoreboard(session); - - for (Objective objective : objectives) { + for (Objective objective : scoreboard.getObjectives().values()) { scoreboard.despawnObjective(objective); } + scoreboard = new Scoreboard(session); } } + public int increaseAndGetScoreboardPacketsPerSecond() { + int pendingPps = scoreboardUpdater.incrementAndGetPacketsPerSecond(); + int pps = scoreboardUpdater.getPacketsPerSecond(); + return Math.max(pps, pendingPps); + } + /** * Tell the client to hide or show the coordinates * diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaScoreboardObjectiveTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaScoreboardObjectiveTranslator.java index ce1ba398..141ff03f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaScoreboardObjectiveTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaScoreboardObjectiveTranslator.java @@ -41,13 +41,11 @@ public class JavaScoreboardObjectiveTranslator extends PacketTranslator { + private static final GeyserLogger LOGGER = GeyserConnector.getInstance().getLogger(); @Override public void translate(ServerTeamPacket packet, GeyserSession session) { - GeyserConnector.getInstance().getLogger().debug("Team packet " + packet.getTeamName() + " " + packet.getAction() + " " + Arrays.toString(packet.getPlayers())); + if (LOGGER.isDebug()) { + LOGGER.debug("Team packet " + packet.getTeamName() + " " + packet.getAction() + " " + Arrays.toString(packet.getPlayers())); + } + + int pps = session.getWorldCache().increaseAndGetScoreboardPacketsPerSecond(); Scoreboard scoreboard = session.getWorldCache().getScoreboard(); Team team = scoreboard.getTeam(packet.getTeamName()); @@ -58,38 +65,53 @@ public class JavaTeamTranslator extends PacketTranslator { .setSuffix(MessageUtils.getTranslatedBedrockMessage(packet.getSuffix(), session.getClientData().getLanguageCode())); break; case UPDATE: - if (team != null) { - team.setName(MessageUtils.getBedrockMessage(packet.getDisplayName())) - .setColor(packet.getColor()) - .setPrefix(MessageUtils.getTranslatedBedrockMessage(packet.getPrefix(), session.getClientData().getLanguageCode())) - .setSuffix(MessageUtils.getTranslatedBedrockMessage(packet.getSuffix(), session.getClientData().getLanguageCode())) - .setUpdateType(UpdateType.UPDATE); - } else { - GeyserConnector.getInstance().getLogger().debug(LanguageUtils.getLocaleStringLog("geyser.network.translator.team.failed_not_registered", packet.getAction(), packet.getTeamName())); + if (team == null) { + LOGGER.debug(LanguageUtils.getLocaleStringLog( + "geyser.network.translator.team.failed_not_registered", + packet.getAction(), packet.getTeamName() + )); + return; } + + team.setName(MessageUtils.getBedrockMessage(packet.getDisplayName())) + .setColor(packet.getColor()) + .setPrefix(MessageUtils.getTranslatedBedrockMessage(packet.getPrefix(), session.getClientData().getLanguageCode())) + .setSuffix(MessageUtils.getTranslatedBedrockMessage(packet.getSuffix(), session.getClientData().getLanguageCode())) + .setUpdateType(UpdateType.UPDATE); break; case ADD_PLAYER: - if (team != null) { - team.addEntities(packet.getPlayers()); - } else { - GeyserConnector.getInstance().getLogger().debug(LanguageUtils.getLocaleStringLog("geyser.network.translator.team.failed_not_registered", packet.getAction(), packet.getTeamName())); + if (team == null) { + LOGGER.debug(LanguageUtils.getLocaleStringLog( + "geyser.network.translator.team.failed_not_registered", + packet.getAction(), packet.getTeamName() + )); + return; } + team.addEntities(packet.getPlayers()); break; case REMOVE_PLAYER: - if (team != null) { - team.removeEntities(packet.getPlayers()); - } else { - GeyserConnector.getInstance().getLogger().debug(LanguageUtils.getLocaleStringLog("geyser.network.translator.team.failed_not_registered", packet.getAction(), packet.getTeamName())); + if (team == null) { + LOGGER.debug(LanguageUtils.getLocaleStringLog( + "geyser.network.translator.team.failed_not_registered", + packet.getAction(), packet.getTeamName() + )); + return; } + team.removeEntities(packet.getPlayers()); break; case REMOVE: scoreboard.removeTeam(packet.getTeamName()); break; } - scoreboard.onUpdate(); + + // ScoreboardUpdater will handle it for us if the packets per second + // (for score and team packets) is higher then the first threshold + if (pps < ScoreboardUpdater.FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD) { + scoreboard.onUpdate(); + } } private Set toPlayerSet(String[] players) { - return new ObjectOpenHashSet<>(Arrays.asList(players)); + return new ObjectOpenHashSet<>(players); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaUpdateScoreTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaUpdateScoreTranslator.java index 8d7d59a8..35033ca5 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaUpdateScoreTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaUpdateScoreTranslator.java @@ -25,48 +25,58 @@ package org.geysermc.connector.network.translators.java.scoreboard; +import com.github.steveice10.mc.protocol.data.game.scoreboard.ScoreboardAction; +import com.github.steveice10.mc.protocol.packet.ingame.server.scoreboard.ServerUpdateScorePacket; import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.GeyserLogger; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.session.cache.WorldCache; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.scoreboard.Objective; import org.geysermc.connector.scoreboard.Scoreboard; - -import com.github.steveice10.mc.protocol.data.game.scoreboard.ScoreboardAction; -import com.github.steveice10.mc.protocol.packet.ingame.server.scoreboard.ServerUpdateScorePacket; +import org.geysermc.connector.scoreboard.ScoreboardUpdater; import org.geysermc.connector.utils.LanguageUtils; @Translator(packet = ServerUpdateScorePacket.class) public class JavaUpdateScoreTranslator extends PacketTranslator { + private final GeyserLogger logger; + + public JavaUpdateScoreTranslator() { + logger = GeyserConnector.getInstance().getLogger(); + } @Override public void translate(ServerUpdateScorePacket packet, GeyserSession session) { - try { - Scoreboard scoreboard = session.getWorldCache().getScoreboard(); + WorldCache worldCache = session.getWorldCache(); + Scoreboard scoreboard = worldCache.getScoreboard(); + int pps = worldCache.increaseAndGetScoreboardPacketsPerSecond(); - Objective objective = scoreboard.getObjective(packet.getObjective()); - if (objective == null && packet.getAction() != ScoreboardAction.REMOVE) { - GeyserConnector.getInstance().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.translator.score.failed_objective", packet.getObjective())); - return; - } + Objective objective = scoreboard.getObjective(packet.getObjective()); + if (objective == null && packet.getAction() != ScoreboardAction.REMOVE) { + logger.info(LanguageUtils.getLocaleStringLog("geyser.network.translator.score.failed_objective", packet.getObjective())); + return; + } - switch (packet.getAction()) { - case ADD_OR_UPDATE: - objective.setScore(packet.getEntry(), packet.getValue()); - break; - case REMOVE: - if (objective != null) { - objective.resetScore(packet.getEntry()); - } else { - for (Objective objective1 : scoreboard.getObjectives().values()) { - objective1.resetScore(packet.getEntry()); - } + switch (packet.getAction()) { + case ADD_OR_UPDATE: + objective.setScore(packet.getEntry(), packet.getValue()); + break; + case REMOVE: + if (objective != null) { + objective.removeScore(packet.getEntry()); + } else { + for (Objective objective1 : scoreboard.getObjectives().values()) { + objective1.removeScore(packet.getEntry()); } - break; - } + } + break; + } + + // ScoreboardUpdater will handle it for us if the packets per second + // (for score and team packets) is higher then the first threshold + if (pps < ScoreboardUpdater.FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD) { scoreboard.onUpdate(); - } catch (Exception ex) { - ex.printStackTrace(); } } } diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/Objective.java b/connector/src/main/java/org/geysermc/connector/scoreboard/Objective.java index c3e6c863..92a1add3 100644 --- a/connector/src/main/java/org/geysermc/connector/scoreboard/Objective.java +++ b/connector/src/main/java/org/geysermc/connector/scoreboard/Objective.java @@ -29,23 +29,23 @@ import com.github.steveice10.mc.protocol.data.game.scoreboard.ScoreboardPosition import lombok.Getter; import lombok.Setter; -import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; @Getter public class Objective { - private Scoreboard scoreboard; - private long id; - private boolean temp; + private final Scoreboard scoreboard; + private final long id; + private boolean active = true; @Setter private UpdateType updateType = UpdateType.ADD; private String objectiveName; - private String displaySlot; + private String displaySlotName; private String displayName = "unknown"; private int type = 0; // 0 = integer, 1 = heart - private Map scores = new HashMap<>(); + private Map scores = new ConcurrentHashMap<>(); private Objective(Scoreboard scoreboard) { this.id = scoreboard.getNextId().getAndIncrement(); @@ -54,23 +54,20 @@ public class Objective { /** * /!\ This method is made for temporary objectives until the real objective is received - * @param scoreboard the scoreboard + * + * @param scoreboard the scoreboard * @param objectiveName the name of the objective */ public Objective(Scoreboard scoreboard, String objectiveName) { this(scoreboard); this.objectiveName = objectiveName; - this.temp = true; + this.active = false; } public Objective(Scoreboard scoreboard, String objectiveName, ScoreboardPosition displaySlot, String displayName, int type) { - this(scoreboard, objectiveName, displaySlot.name().toLowerCase(), displayName, type); - } - - public Objective(Scoreboard scoreboard, String objectiveName, String displaySlot, String displayName, int type) { this(scoreboard); this.objectiveName = objectiveName; - this.displaySlot = displaySlot; + this.displaySlotName = translateDisplaySlot(displaySlot); this.displayName = displayName; this.type = type; } @@ -87,21 +84,9 @@ public class Objective { public void setScore(String id, int score) { if (scores.containsKey(id)) { scores.get(id).setScore(score).setUpdateType(UpdateType.ADD); - } else { - registerScore(id, score); + return; } - } - - public void setScoreText(String oldText, String newText) { - if (!scores.containsKey(oldText) || oldText.equals(newText)) return; - Score oldScore = scores.get(oldText); - - Score newScore = new Score(this, newText) - .setScore(oldScore.getScore()) - .setTeam(scoreboard.getTeamFor(newText)); - - scores.put(newText, newScore); - oldScore.setUpdateType(UpdateType.REMOVE); + registerScore(id, score); } public int getScore(String id) { @@ -113,37 +98,61 @@ public class Objective { public Score getScore(int line) { for (Score score : scores.values()) { - if (score.getScore() == line) return score; + if (score.getScore() == line) { + return score; + } } return null; } - public void resetScore(String id) { + public void removeScore(String id) { if (scores.containsKey(id)) { scores.get(id).setUpdateType(UpdateType.REMOVE); } } - public void removeScore(String id) { + /** + * Used internally to remove a score from the score map + */ + public void removeScore0(String id) { scores.remove(id); } public Objective setDisplayName(String displayName) { this.displayName = displayName; - if (updateType == UpdateType.NOTHING) updateType = UpdateType.UPDATE; + if (updateType == UpdateType.NOTHING) { + updateType = UpdateType.UPDATE; + } return this; } public Objective setType(int type) { this.type = type; - if (updateType == UpdateType.NOTHING) updateType = UpdateType.UPDATE; + if (updateType == UpdateType.NOTHING) { + updateType = UpdateType.UPDATE; + } return this; } - public void removeTemp(ScoreboardPosition displaySlot) { - if (temp) { - temp = false; - this.displaySlot = displaySlot.name().toLowerCase(); + public void setActive(ScoreboardPosition displaySlot) { + if (!active) { + active = true; + displaySlotName = translateDisplaySlot(displaySlot); + } + } + + public void removed() { + scores = null; + } + + private static String translateDisplaySlot(ScoreboardPosition displaySlot) { + switch (displaySlot) { + case BELOW_NAME: + return "belowname"; + case PLAYER_LIST: + return "list"; + default: + return "sidebar"; } } } diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/Score.java b/connector/src/main/java/org/geysermc/connector/scoreboard/Score.java index d5a65e8c..635bafa3 100644 --- a/connector/src/main/java/org/geysermc/connector/scoreboard/Score.java +++ b/connector/src/main/java/org/geysermc/connector/scoreboard/Score.java @@ -25,40 +25,63 @@ package org.geysermc.connector.scoreboard; +import com.nukkitx.protocol.bedrock.data.ScoreInfo; import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; -@Getter @Setter +@Getter @Accessors(chain = true) public class Score { - private Objective objective; - private long id; + private final Objective objective; + private ScoreInfo cachedInfo; + private final long id; + @Setter private UpdateType updateType = UpdateType.ADD; - private String name; + private final String name; private Team team; private int score; + @Setter private int oldScore = Integer.MIN_VALUE; public Score(Objective objective, String name) { this.id = objective.getScoreboard().getNextId().getAndIncrement(); this.objective = objective; this.name = name; + update(); } public String getDisplayName() { - if (team != null && team.getUpdateType() != UpdateType.REMOVE) { + if (team != null) { return team.getPrefix() + name + team.getSuffix(); } return name; } public Score setScore(int score) { - if (oldScore == Integer.MIN_VALUE) { - this.oldScore = score; - } this.score = score; + updateType = UpdateType.UPDATE; return this; } + + public Score setTeam(Team team) { + if (this.team != null && team != null) { + if (!this.team.equals(team)) { + this.team = team; + updateType = UpdateType.UPDATE; + } + return this; + } + // simplified from (this.team != null && team == null) || (this.team == null && team != null) + if (this.team != null || team != null) { + this.team = team; + updateType = UpdateType.UPDATE; + } + return this; + } + + public void update() { + cachedInfo = new ScoreInfo(id, objective.getObjectiveName(), score, getDisplayName()); + } } diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java b/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java index 5fdda617..9f89d9d2 100644 --- a/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java +++ b/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java @@ -26,73 +26,72 @@ package org.geysermc.connector.scoreboard; import com.github.steveice10.mc.protocol.data.game.scoreboard.ScoreboardPosition; +import com.nukkitx.protocol.bedrock.BedrockPacket; import com.nukkitx.protocol.bedrock.data.ScoreInfo; import com.nukkitx.protocol.bedrock.packet.RemoveObjectivePacket; import com.nukkitx.protocol.bedrock.packet.SetDisplayObjectivePacket; import com.nukkitx.protocol.bedrock.packet.SetScorePacket; -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; 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.LanguageUtils; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; import static org.geysermc.connector.scoreboard.UpdateType.*; @Getter public class Scoreboard { - private GeyserSession session; - private AtomicLong nextId = new AtomicLong(0); + private final GeyserSession session; + private final GeyserLogger logger; + private final AtomicLong nextId = new AtomicLong(0); - private Map objectives = new HashMap<>(); - private Map teams = new HashMap<>(); + private final Map objectives = new ConcurrentHashMap<>(); + private final Map teams = new HashMap<>(); + + private int lastScoreCount = 0; public Scoreboard(GeyserSession session) { this.session = session; + this.logger = GeyserConnector.getInstance().getLogger(); } - public Objective registerNewObjective(String objectiveId, boolean temp) { - if (!temp || objectives.containsKey(objectiveId)) return objectives.get(objectiveId); + public Objective registerNewObjective(String objectiveId, boolean active) { + if (active || objectives.containsKey(objectiveId)) { + return objectives.get(objectiveId); + } Objective objective = new Objective(this, objectiveId); objectives.put(objectiveId, objective); return objective; } public Objective registerNewObjective(String objectiveId, ScoreboardPosition displaySlot) { - Objective objective = null; - if (objectives.containsKey(objectiveId)) { - objective = objectives.get(objectiveId); - if (objective.isTemp()) objective.removeTemp(displaySlot); - else { - despawnObjective(objective); - objective = null; + Objective objective = objectives.get(objectiveId); + if (objective != null) { + if (!objective.isActive()) { + objective.setActive(displaySlot); + return objective; } + despawnObjective(objective); } - if (objective == null) { - objective = new Objective(this, objectiveId, displaySlot, "unknown", 0); - objectives.put(objectiveId, objective); - } + + objective = new Objective(this, objectiveId, displaySlot, "unknown", 0); + objectives.put(objectiveId, objective); return objective; } public Team registerNewTeam(String teamName, Set players) { - if (teams.containsKey(teamName)) { - session.getConnector().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.translator.team.failed_overrides", teamName)); - return getTeam(teamName); + Team team = teams.get(teamName); + if (team != null) { + logger.info(LanguageUtils.getLocaleStringLog("geyser.network.translator.team.failed_overrides", teamName)); + return team; } - Team team = new Team(this, teamName).setEntities(players); + team = new Team(this, teamName).setEntities(players); teams.put(teamName, team); - - for (Objective objective : objectives.values()) { - for (Score score : objective.getScores().values()) { - if (players.contains(score.getName())) { - score.setTeam(team); - } - } - } return team; } @@ -106,102 +105,119 @@ public class Scoreboard { public void unregisterObjective(String objectiveName) { Objective objective = getObjective(objectiveName); - if (objective != null) objective.setUpdateType(REMOVE); + if (objective != null) { + objective.setUpdateType(REMOVE); + } } public void removeTeam(String teamName) { Team remove = teams.remove(teamName); - if (remove != null) remove.setUpdateType(REMOVE); + if (remove != null) { + remove.setUpdateType(REMOVE); + } } public void onUpdate() { - Set changedObjectives = new ObjectOpenHashSet<>(); - List addScores = new ArrayList<>(); - List removeScores = new ArrayList<>(); + List addScores = new ArrayList<>(getLastScoreCount()); + List removeScores = new ArrayList<>(getLastScoreCount()); - for (String objectiveId : new ArrayList<>(objectives.keySet())) { - Objective objective = objectives.get(objectiveId); - if (objective.isTemp()) { - session.getConnector().getLogger().debug("Ignoring temp Scoreboard Objective '"+ objectiveId +'\''); + for (Objective objective : objectives.values()) { + if (!objective.isActive()) { + logger.debug("Ignoring non-active Scoreboard Objective '"+ objective.getObjectiveName() +'\''); continue; } - if (objective.getUpdateType() != NOTHING) changedObjectives.add(objective); + // hearts can't hold teams, so we treat them differently + if (objective.getType() == 1) { + for (Score score : objective.getScores().values()) { + if (score.getUpdateType() == NOTHING) { + continue; + } + + boolean update = score.getUpdateType() == UPDATE; + if (update) { + score.update(); + } + + if (score.getUpdateType() == ADD || update) { + addScores.add(score.getCachedInfo()); + } + if (score.getUpdateType() == REMOVE || update) { + removeScores.add(score.getCachedInfo()); + } + } + continue; + } boolean globalUpdate = objective.getUpdateType() == UPDATE; - boolean globalAdd = objective.getUpdateType() == ADD || globalUpdate; - boolean globalRemove = objective.getUpdateType() == REMOVE || globalUpdate; + boolean globalAdd = objective.getUpdateType() == ADD; + boolean globalRemove = objective.getUpdateType() == REMOVE; - boolean hasUpdate = globalUpdate; - - List handledScores = new ArrayList<>(); - for (String identifier : new ObjectOpenHashSet<>(objective.getScores().keySet())) { - Score score = objective.getScores().get(identifier); + for (Score score : objective.getScores().values()) { Team team = score.getTeam(); - boolean inTeam = team != null && team.getEntities().contains(score.getName()); + boolean add = globalAdd || globalUpdate; + boolean remove = globalRemove; + boolean teamChanged = false; + if (team != null) { + if (team.getUpdateType() == REMOVE || !team.hasEntity(score.getName())) { + score.setTeam(null); + teamChanged = true; + } - boolean teamAdd = team != null && (team.getUpdateType() == ADD || team.getUpdateType() == UPDATE); - boolean teamRemove = team != null && (team.getUpdateType() == REMOVE || team.getUpdateType() == UPDATE); + teamChanged |= team.getUpdateType() == UPDATE; - if (team != null && (team.getUpdateType() == REMOVE || !inTeam)) score.setTeam(null); - - boolean add = (hasUpdate || globalAdd || teamAdd || teamRemove || score.getUpdateType() == ADD || score.getUpdateType() == UPDATE) && (score.getUpdateType() != REMOVE); - boolean remove = hasUpdate || globalRemove || teamAdd || teamRemove || score.getUpdateType() == REMOVE || score.getUpdateType() == UPDATE; - - boolean updated = false; - if (!hasUpdate) { - updated = hasUpdate = add; + add |= team.getUpdateType() == ADD || team.getUpdateType() == UPDATE; + remove |= team.getUpdateType() == REMOVE; } - if (updated) { - for (Score score1 : handledScores) { - ScoreInfo scoreInfo = new ScoreInfo(score1.getId(), score1.getObjective().getObjectiveName(), score1.getScore(), score1.getDisplayName()); - addScores.add(scoreInfo); - removeScores.add(scoreInfo); - } + add |= score.getUpdateType() == ADD || score.getUpdateType() == UPDATE; + remove |= score.getUpdateType() == REMOVE; + if (score.getUpdateType() == REMOVE) { + add = false; + } + + if (score.getUpdateType() == UPDATE || teamChanged) { + score.update(); } if (add) { - addScores.add(new ScoreInfo(score.getId(), score.getObjective().getObjectiveName(), score.getScore(), score.getDisplayName())); + addScores.add(score.getCachedInfo()); } if (remove) { - removeScores.add(new ScoreInfo(score.getId(), score.getObjective().getObjectiveName(), score.getOldScore(), score.getDisplayName())); + removeScores.add(score.getCachedInfo()); } + // score is pending to be updated, so we use the current score as the old score score.setOldScore(score.getScore()); + // score is pending to be removed, so we can remove it from the objective if (score.getUpdateType() == REMOVE) { - objective.removeScore(score.getName()); + objective.removeScore0(score.getName()); } - if (add || remove) { - changedObjectives.add(objective); - } else { // stays the same like before - handledScores.add(score); - } score.setUpdateType(NOTHING); } - } - for (Objective objective : changedObjectives) { - boolean update = objective.getUpdateType() == NOTHING || objective.getUpdateType() == UPDATE; - if (objective.getUpdateType() == REMOVE || update) { + if (globalRemove || globalUpdate) { RemoveObjectivePacket removeObjectivePacket = new RemoveObjectivePacket(); removeObjectivePacket.setObjectiveId(objective.getObjectiveName()); session.sendUpstreamPacket(removeObjectivePacket); if (objective.getUpdateType() == REMOVE) { objectives.remove(objective.getObjectiveName()); // now we can deregister + objective.removed(); } } - if (objective.getUpdateType() == ADD || update) { + + if (globalAdd || globalUpdate) { SetDisplayObjectivePacket displayObjectivePacket = new SetDisplayObjectivePacket(); displayObjectivePacket.setObjectiveId(objective.getObjectiveName()); displayObjectivePacket.setDisplayName(objective.getDisplayName()); displayObjectivePacket.setCriteria("dummy"); - displayObjectivePacket.setDisplaySlot(objective.getDisplaySlot()); + displayObjectivePacket.setDisplaySlot(objective.getDisplaySlotName()); displayObjectivePacket.setSortOrder(1); // ?? session.sendUpstreamPacket(displayObjectivePacket); } + objective.setUpdateType(NOTHING); } @@ -218,6 +234,8 @@ public class Scoreboard { setScorePacket.setInfos(addScores); session.sendUpstreamPacket(setScorePacket); } + + lastScoreCount = addScores.size(); } public void despawnObjective(Objective objective) { @@ -234,6 +252,8 @@ public class Scoreboard { 0, "" )); } + + objective.removed(); if (!toRemove.isEmpty()) { SetScorePacket setScorePacket = new SetScorePacket(); diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/ScoreboardUpdater.java b/connector/src/main/java/org/geysermc/connector/scoreboard/ScoreboardUpdater.java new file mode 100644 index 00000000..ee38cecd --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/scoreboard/ScoreboardUpdater.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.scoreboard; + +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.configuration.GeyserConfiguration; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.session.cache.WorldCache; +import org.geysermc.connector.utils.LanguageUtils; + +import java.util.concurrent.atomic.AtomicInteger; + +public class ScoreboardUpdater extends Thread { + public static final int FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD; + public static final int SECOND_SCORE_PACKETS_PER_SECOND_THRESHOLD = 250; + + private static final int FIRST_MILLIS_BETWEEN_UPDATES = 250; // 4 updates per second + private static final int SECOND_MILLIS_BETWEEN_UPDATES = 1000 * 3; // 1 update per 3 seconds + + private static final boolean DEBUG_ENABLED; + + private final WorldCache worldCache; + private final GeyserSession session; + + private int millisBetweenUpdates = FIRST_MILLIS_BETWEEN_UPDATES; + private long lastUpdate = System.currentTimeMillis(); + private long lastLog = -1; + + private long lastPacketsPerSecondUpdate = System.currentTimeMillis(); + private final AtomicInteger packetsPerSecond = new AtomicInteger(0); + private final AtomicInteger pendingPacketsPerSecond = new AtomicInteger(0); + + public ScoreboardUpdater(WorldCache worldCache) { + super("Scoreboard Updater"); + this.worldCache = worldCache; + session = worldCache.getSession(); + } + + @Override + public void run() { + while (!session.isClosed()) { + long currentTime = System.currentTimeMillis(); + + // reset score-packets per second every second + if (currentTime - lastPacketsPerSecondUpdate > 1000) { + lastPacketsPerSecondUpdate = currentTime; + packetsPerSecond.set(pendingPacketsPerSecond.get()); + pendingPacketsPerSecond.set(0); + } + + if (currentTime - lastUpdate > millisBetweenUpdates) { + lastUpdate = currentTime; + + int pps = packetsPerSecond.get(); + if (pps >= FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD) { + boolean reachedSecondThreshold = pps >= SECOND_SCORE_PACKETS_PER_SECOND_THRESHOLD; + if (reachedSecondThreshold) { + millisBetweenUpdates = SECOND_MILLIS_BETWEEN_UPDATES; + } else { + millisBetweenUpdates = FIRST_MILLIS_BETWEEN_UPDATES; + } + + worldCache.getScoreboard().onUpdate(); + + if (DEBUG_ENABLED && (currentTime - lastLog > 60000)) { // one minute + int threshold = reachedSecondThreshold ? + SECOND_SCORE_PACKETS_PER_SECOND_THRESHOLD : + FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD; + + GeyserConnector.getInstance().getLogger().info( + LanguageUtils.getLocaleStringLog("geyser.scoreboard.updater.threshold_reached.log", session.getName(), threshold, pps) + + LanguageUtils.getLocaleStringLog("geyser.scoreboard.updater.threshold_reached", (millisBetweenUpdates / 1000.0)) + ); + + lastLog = currentTime; + } + } + } + } + } + + public int getPacketsPerSecond() { + return packetsPerSecond.get(); + } + + /** + * Increase the Scoreboard Packets Per Second and return the updated value + */ + public int incrementAndGetPacketsPerSecond() { + return pendingPacketsPerSecond.incrementAndGet(); + } + + static { + GeyserConfiguration config = GeyserConnector.getInstance().getConfig(); + FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD = Math.min(config.getScoreboardPacketThreshold(), SECOND_SCORE_PACKETS_PER_SECOND_THRESHOLD); + DEBUG_ENABLED = config.isDebugMode(); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java b/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java index c2fcc02c..087ea2dd 100644 --- a/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java +++ b/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java @@ -35,8 +35,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Set; -@Getter -@Setter +@Getter @Setter @Accessors(chain = true) public class Team { private final Scoreboard scoreboard; @@ -44,6 +43,7 @@ public class Team { private UpdateType updateType = UpdateType.ADD; private String name; + private String prefix; private TeamColor color; private String suffix; @@ -57,8 +57,7 @@ public class Team { public void addEntities(String... names) { List added = new ArrayList<>(); for (String name : names) { - if (!entities.contains(name)) { - entities.add(name); + if (entities.add(name)) { added.add(name); } } @@ -78,4 +77,35 @@ public class Team { } setUpdateType(UpdateType.UPDATE); } + + public boolean hasEntity(String name) { + return entities.contains(name); + } + + public Team setPrefix(String prefix) { + // replace "null" to an empty string, + // we do this here to improve the performance of Score#getDisplayName + if (prefix.length() == 4 && "null".equals(prefix)) { + this.prefix = ""; + return this; + } + this.prefix = prefix; + return this; + } + + public Team setSuffix(String suffix) { + // replace "null" to an empty string, + // we do this here to improve the performance of Score#getDisplayName + if (suffix.length() == 4 && "null".equals(suffix)) { + this.suffix = ""; + return this; + } + this.suffix = suffix; + return this; + } + + @Override + public int hashCode() { + return id.hashCode(); + } } diff --git a/connector/src/main/resources/config.yml b/connector/src/main/resources/config.yml index af588185..bde95d24 100644 --- a/connector/src/main/resources/config.yml +++ b/connector/src/main/resources/config.yml @@ -124,6 +124,12 @@ metrics: # ADVANCED OPTIONS - DO NOT TOUCH UNLESS YOU KNOW WHAT YOU ARE DOING! +# Geyser updates the Scoreboard after every Scoreboard packet, but when Geyser tries to handle +# a lot of scoreboard packets per second can cause serious lag. +# This option allows you to specify after how many Scoreboard packets per seconds +# the Scoreboard updates will be limited to four updates per second. +scoreboard-packet-threshold: 20 + # Allow connections from ProxyPass and Waterdog. # See https://www.spigotmc.org/wiki/firewall-guide/ for assistance - use UDP instead of TCP. enable-proxy-connections: false From 79bf56a75cdc04f58748a4c819555937a2908499 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Fri, 28 Aug 2020 19:36:24 +0100 Subject: [PATCH 004/205] Tweaks to support Android (#1206) * Downgrade reflections to 0.9.11 * Add comment explaining downgrade * Move to pre-build reflections * Update skins to use https and relative cache dir * Move to https OptiFine cape url * Add javadoc to isProduction * Add ANDROID as a platform type * Re-ordered PlatformType * Change stop command to call onDisable --- connector/pom.xml | 53 ++++++++++++++++++- .../geysermc/connector/GeyserConnector.java | 14 ++++- .../command/defaults/StopCommand.java | 6 +-- .../connector/common/PlatformType.java | 1 + .../translators/PacketTranslatorRegistry.java | 3 +- .../translators/item/ItemTranslator.java | 3 +- .../sound/SoundHandlerRegistry.java | 4 +- .../world/block/BlockTranslator.java | 2 +- .../block/entity/BlockEntityTranslator.java | 3 +- .../geysermc/connector/utils/FileUtils.java | 21 ++++++++ .../geysermc/connector/utils/LocaleUtils.java | 2 +- .../connector/utils/SkinProvider.java | 6 +-- .../geysermc/connector/utils/SkinUtils.java | 4 +- 13 files changed, 104 insertions(+), 18 deletions(-) diff --git a/connector/pom.xml b/connector/pom.xml index a3748044..e2c01e64 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -129,7 +129,12 @@ org.reflections reflections - 0.9.12 + 0.9.11 + + + org.dom4j + dom4j + 2.1.3 net.kyori @@ -232,6 +237,52 @@ + + org.codehaus.gmavenplus + gmavenplus-plugin + 1.9.1 + + + process-classes + + execute + + + + + + + + + + + org.reflections + reflections + 0.9.11 + + + org.dom4j + dom4j + 2.1.3 + + + org.codehaus.groovy + groovy-all + 3.0.5 + runtime + pom + + + diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index ec332ed6..2593c516 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -163,7 +163,7 @@ public class GeyserConnector { config.getRemote().setPort(remotePort = Integer.parseInt(record[2])); logger.debug("Found SRV record \"" + remoteAddress + ":" + remotePort + "\""); } - } catch (Exception ex) { + } catch (Exception | NoClassDefFoundError ex) { // Check for a NoClassDefFoundError to prevent Android crashes logger.debug("Exception while trying to find an SRV record for the remote host."); if (config.isDebugMode()) ex.printStackTrace(); // Otherwise we can get a stack trace for any domain that doesn't have an SRV record @@ -307,6 +307,18 @@ public class GeyserConnector { return bootstrap.getWorldManager(); } + /** + * Get the production status of the current runtime. + * Will return true if the version number is not 'DEV'. + * Should only happen in compiled jars. + * + * @return If we are in a production build/environment + */ + public boolean isProduction() { + //noinspection ConstantConditions + return !"DEV".equals(GeyserConnector.VERSION); + } + public static GeyserConnector getInstance() { return instance; } diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/StopCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/StopCommand.java index 636058a0..c69a8705 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/StopCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/StopCommand.java @@ -49,10 +49,6 @@ public class StopCommand extends GeyserCommand { return; } - connector.shutdown(); - - if (connector.getPlatformType() == PlatformType.STANDALONE) { - System.exit(0); - } + connector.getBootstrap().onDisable(); } } diff --git a/connector/src/main/java/org/geysermc/connector/common/PlatformType.java b/connector/src/main/java/org/geysermc/connector/common/PlatformType.java index 7c245c9b..4daa5d37 100644 --- a/connector/src/main/java/org/geysermc/connector/common/PlatformType.java +++ b/connector/src/main/java/org/geysermc/connector/common/PlatformType.java @@ -32,6 +32,7 @@ import lombok.Getter; @AllArgsConstructor public enum PlatformType { + ANDROID("Android"), BUNGEECORD("BungeeCord"), SPIGOT("Spigot"), SPONGE("Sponge"), 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 92d2e910..c4386481 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/PacketTranslatorRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/PacketTranslatorRegistry.java @@ -33,6 +33,7 @@ import com.nukkitx.protocol.bedrock.BedrockPacket; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.utils.FileUtils; import org.geysermc.connector.utils.LanguageUtils; import org.reflections.Reflections; @@ -48,7 +49,7 @@ public class PacketTranslatorRegistry { private static final ObjectArrayList> IGNORED_PACKETS = new ObjectArrayList<>(); static { - Reflections ref = new Reflections("org.geysermc.connector.network.translators"); + Reflections ref = GeyserConnector.getInstance().isProduction() ? FileUtils.getReflections("org.geysermc.connector.network.translators") : new Reflections("org.geysermc.connector.network.translators"); for (Class clazz : ref.getTypesAnnotatedWith(Translator.class)) { Class packet = clazz.getAnnotation(Translator.class).packet(); 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 dce0f4b4..c0853120 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 @@ -41,6 +41,7 @@ import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.ItemRemapper; +import org.geysermc.connector.utils.FileUtils; import org.geysermc.connector.utils.LanguageUtils; import org.geysermc.connector.utils.MessageUtils; import org.reflections.Reflections; @@ -62,7 +63,7 @@ public abstract class ItemTranslator { static { /* Load item translators */ - Reflections ref = new Reflections("org.geysermc.connector.network.translators.item"); + Reflections ref = GeyserConnector.getInstance().isProduction() ? FileUtils.getReflections("org.geysermc.connector.network.translators.item") : new Reflections("org.geysermc.connector.network.translators.item"); Map loadedNbtItemTranslators = new HashMap<>(); for (Class clazz : ref.getTypesAnnotatedWith(ItemRemapper.class)) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/sound/SoundHandlerRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/sound/SoundHandlerRegistry.java index 893975e5..163c451c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/sound/SoundHandlerRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/sound/SoundHandlerRegistry.java @@ -25,6 +25,8 @@ package org.geysermc.connector.network.translators.sound; +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.utils.FileUtils; import org.reflections.Reflections; import java.util.HashMap; @@ -38,7 +40,7 @@ public class SoundHandlerRegistry { static final Map> INTERACTION_HANDLERS = new HashMap<>(); static { - Reflections ref = new Reflections("org.geysermc.connector.network.translators.sound"); + Reflections ref = GeyserConnector.getInstance().isProduction() ? FileUtils.getReflections("org.geysermc.connector.network.translators.sound") : new Reflections("org.geysermc.connector.network.translators.sound"); for (Class clazz : ref.getTypesAnnotatedWith(SoundHandler.class)) { try { SoundInteractionHandler interactionHandler = (SoundInteractionHandler) clazz.newInstance(); 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 e627b845..8da09f6f 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 @@ -106,7 +106,7 @@ public class BlockTranslator { addedStatesMap.defaultReturnValue(-1); List paletteList = new ArrayList<>(); - Reflections ref = new Reflections("org.geysermc.connector.network.translators.world.block.entity"); + Reflections ref = GeyserConnector.getInstance().isProduction() ? FileUtils.getReflections("org.geysermc.connector.network.translators.world.block.entity") : new Reflections("org.geysermc.connector.network.translators.world.block.entity"); ref.getTypesAnnotatedWith(BlockEntity.class); int waterRuntimeId = -1; 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 c4401c4c..a538c2dc 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java @@ -34,6 +34,7 @@ import com.nukkitx.nbt.NbtMapBuilder; 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; @@ -66,7 +67,7 @@ public abstract class BlockEntityTranslator { } static { - Reflections ref = new Reflections("org.geysermc.connector.network.translators.world.block.entity"); + Reflections ref = GeyserConnector.getInstance().isProduction() ? FileUtils.getReflections("org.geysermc.connector.network.translators.world.block.entity") : new Reflections("org.geysermc.connector.network.translators.world.block.entity"); for (Class clazz : ref.getTypesAnnotatedWith(BlockEntity.class)) { GeyserConnector.getInstance().getLogger().debug("Found annotated block entity: " + clazz.getCanonicalName()); 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 38369d6c..300946b3 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java @@ -28,11 +28,15 @@ package org.geysermc.connector.utils; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import org.geysermc.connector.GeyserConnector; +import org.reflections.Reflections; +import org.reflections.serializers.XmlSerializer; +import org.reflections.util.ConfigurationBuilder; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.net.URL; import java.util.function.Function; public class FileUtils { @@ -140,4 +144,21 @@ public class FileUtils { } return stream; } + + /** + * Get the stored reflection data for a given path + * + * @param path The path to get the reflection data for + * @return The created Reflections object + */ + public static Reflections getReflections(String path) { + Reflections reflections = new Reflections(new ConfigurationBuilder()); + XmlSerializer serializer = new XmlSerializer(); + URL resource = FileUtils.class.getClassLoader().getResource("META-INF/reflections/" + path + "-reflections.xml"); + try (InputStream inputStream = resource.openConnection().getInputStream()) { + reflections.merge(serializer.read(inputStream)); + } catch (IOException e) { } + + return reflections; + } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java index 285846a9..c09af2b7 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java @@ -150,7 +150,7 @@ public class LocaleUtils { // Get the hash and download the locale String hash = ASSET_MAP.get("minecraft/lang/" + locale + ".json").getHash(); - WebUtils.downloadFile("http://resources.download.minecraft.net/" + hash.substring(0, 2) + "/" + hash, localeFile.toString()); + WebUtils.downloadFile("https://resources.download.minecraft.net/" + hash.substring(0, 2) + "/" + hash, localeFile.toString()); } /** 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 5551230b..7c30e48a 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java @@ -119,7 +119,7 @@ public class SkinProvider { // Schedule Daily Image Expiry if we are caching them if (GeyserConnector.getInstance().getConfig().getCacheImages() > 0) { GeyserConnector.getInstance().getGeneralThreadPool().scheduleAtFixedRate(() -> { - File cacheFolder = Paths.get("cache", "images").toFile(); + File cacheFolder = GeyserConnector.getInstance().getBootstrap().getConfigFolder().resolve("cache").resolve("images").toFile(); if (!cacheFolder.exists()) { return; } @@ -395,7 +395,7 @@ public class SkinProvider { BufferedImage image = null; // First see if we have a cached file. We also update the modification stamp so we know when the file was last used - File imageFile = Paths.get("cache", "images", UUID.nameUUIDFromBytes(imageUrl.getBytes()).toString() + ".png").toFile(); + File imageFile = GeyserConnector.getInstance().getBootstrap().getConfigFolder().resolve("cache").resolve("images").resolve(UUID.nameUUIDFromBytes(imageUrl.getBytes()).toString() + ".png").toFile(); if (imageFile.exists()) { try { GeyserConnector.getInstance().getLogger().debug("Reading cached image from file " + imageFile.getPath() + " for " + imageUrl); @@ -600,7 +600,7 @@ public class SkinProvider { @Getter public enum CapeProvider { MINECRAFT, - OPTIFINE("http://s.optifine.net/capes/%s.png", CapeUrlType.USERNAME), + OPTIFINE("https://optifine.net/capes/%s.png", CapeUrlType.USERNAME), LABYMOD("https://www.labymod.net/page/php/getCapeTexture.php?uuid=%s", CapeUrlType.UUID_DASHED), FIVEZIG("https://textures.5zigreborn.eu/profile/%s", CapeUrlType.UUID_DASHED), MINECRAFTCAPES("https://minecraftcapes.net/profile/%s/cape", CapeUrlType.UUID); diff --git a/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java b/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java index f0b88c43..e3488d6e 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java @@ -150,14 +150,14 @@ public class SkinUtils { JsonNode textures = skinObject.get("textures"); JsonNode skinTexture = textures.get("SKIN"); - String skinUrl = skinTexture.get("url").asText(); + String skinUrl = skinTexture.get("url").asText().replace("http://", "https://"); isAlex = skinTexture.has("metadata"); String capeUrl = null; if (textures.has("CAPE")) { JsonNode capeTexture = textures.get("CAPE"); - capeUrl = capeTexture.get("url").asText(); + capeUrl = capeTexture.get("url").asText().replace("http://", "https://"); } return new GameProfileData(skinUrl, capeUrl, isAlex); From 3f0080349986ac687e152f5fbb12f4f62b0d24cf Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Fri, 28 Aug 2020 15:16:35 -0400 Subject: [PATCH 005/205] connector/pom.xml: Move back to main MCProtocolLib repository (#1211) Fixes a regression where ClientPlayerAbilitiesPacket was sending the incorrect flag. --- connector/pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/connector/pom.xml b/connector/pom.xml index e2c01e64..76a83c23 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -109,9 +109,9 @@ compile - com.github.GeyserMC - MCProtocolLib - f37c98dc70 + com.github.steveice10 + mcprotocollib + b3cf3acbb3 compile From caa5098d2c9ad44c0121fec076c3899fe3e6c2be Mon Sep 17 00:00:00 2001 From: jackson-57 <49173011+jackson-57@users.noreply.github.com> Date: Fri, 28 Aug 2020 13:58:37 -0700 Subject: [PATCH 006/205] Fix minor spelling error in issue template (#1214) --- .github/ISSUE_TEMPLATE/feature_request.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 75a76cd0..46653e62 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -11,4 +11,4 @@ assignees: '' Add a description **Alternatives?** -Any alternatives you have tryed +List any alternatives you might have tried From 37c4192c1270c1e9bbe13e7bcc76c052a456f308 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Fri, 28 Aug 2020 19:11:32 -0500 Subject: [PATCH 007/205] Show the supported Bedrock versions in the version command rather than just the default codec --- .../connector/command/defaults/VersionCommand.java | 12 +++++++++++- .../geysermc/connector/network/BedrockProtocol.java | 11 ++++++----- connector/src/main/resources/mappings | 2 +- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/VersionCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/VersionCommand.java index 681474a9..f7f62e59 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/VersionCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/VersionCommand.java @@ -26,6 +26,7 @@ package org.geysermc.connector.command.defaults; import com.github.steveice10.mc.protocol.MinecraftConstants; +import com.nukkitx.protocol.bedrock.BedrockPacketCodec; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.CommandSender; import org.geysermc.connector.command.GeyserCommand; @@ -38,6 +39,7 @@ import org.geysermc.connector.utils.WebUtils; import java.io.IOException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.util.List; import java.util.Properties; public class VersionCommand extends GeyserCommand { @@ -51,7 +53,15 @@ public class VersionCommand extends GeyserCommand { @Override public void execute(CommandSender sender, String[] args) { - sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.version.version", GeyserConnector.NAME, GeyserConnector.VERSION, MinecraftConstants.GAME_VERSION, BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion())); + String bedrockVersions; + List supportedCodecs = BedrockProtocol.SUPPORTED_BEDROCK_CODECS; + if (supportedCodecs.size() > 1) { + bedrockVersions = supportedCodecs.get(0).getMinecraftVersion() + " - " + supportedCodecs.get(supportedCodecs.size() - 1).getMinecraftVersion(); + } else { + bedrockVersions = BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion(); + } + + sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.version.version", GeyserConnector.NAME, GeyserConnector.VERSION, MinecraftConstants.GAME_VERSION, bedrockVersions)); // Disable update checking in dev mode //noinspection ConstantConditions - changes in production 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 904f70a0..f040fc1f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java +++ b/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java @@ -30,25 +30,26 @@ import com.nukkitx.protocol.bedrock.v407.Bedrock_v407; import com.nukkitx.protocol.bedrock.v408.Bedrock_v408; import com.nukkitx.protocol.bedrock.v409.Bedrock_v409; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; +import java.util.ArrayList; +import java.util.List; /** * Contains information about the supported Bedrock protocols in Geyser. */ public class BedrockProtocol { /** - * Default Bedrock codec that should act as a fallback and as the version shown in /geyser version + * Default Bedrock codec that should act as a fallback. Should represent the latest available + * release of the game that Geyser supports. */ public static final BedrockPacketCodec DEFAULT_BEDROCK_CODEC = Bedrock_v408.V408_CODEC; /** * A list of all supported Bedrock versions that can join Geyser */ - public static final Set SUPPORTED_BEDROCK_CODECS = ConcurrentHashMap.newKeySet(); + public static final List SUPPORTED_BEDROCK_CODECS = new ArrayList<>(); static { - SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC); SUPPORTED_BEDROCK_CODECS.add(Bedrock_v407.V407_CODEC); + SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC); SUPPORTED_BEDROCK_CODECS.add(Bedrock_v409.V409_CODEC); } diff --git a/connector/src/main/resources/mappings b/connector/src/main/resources/mappings index ec8b6829..15958f16 160000 --- a/connector/src/main/resources/mappings +++ b/connector/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit ec8b68297c4d62a9e4d640a8c7e77977062628ee +Subproject commit 15958f16ca36c9536490f8af67f556eba5665dcb From 2f9ff0c6220376f276ca46f1e038411a834da356 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Sun, 30 Aug 2020 00:18:23 -0400 Subject: [PATCH 008/205] ShulkerBoxItemTranslator: Ensure Items ListTag is present (#1221) * ShulkerBoxItemTranslator: Ensure Items ListTag is present * Compile --- .../connector/network/translators/item/ItemTranslator.java | 4 ++-- .../item/translators/nbt/ShulkerBoxItemTranslator.java | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java index c0853120..13725522 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 @@ -35,7 +35,7 @@ import com.nukkitx.nbt.NbtType; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import org.geysermc.connector.GeyserConnector; @@ -358,7 +358,7 @@ public abstract class ItemTranslator { // If its not a message convert it if (!MessageUtils.isMessage(name)) { - TextComponent component = LegacyComponentSerializer.legacySection().deserialize(name); + Component component = LegacyComponentSerializer.legacySection().deserialize(name); name = GsonComponentSerializer.gson().serialize(component); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java index 1780537c..0645c980 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java @@ -42,6 +42,7 @@ public class ShulkerBoxItemTranslator extends NbtItemStackTranslator { if (!itemTag.contains("BlockEntityTag")) return; // Empty shulker box CompoundTag blockEntityTag = itemTag.get("BlockEntityTag"); + if (blockEntityTag.get("Items") == null) return; ListTag itemsList = new ListTag("Items"); for (Tag item : (ListTag) blockEntityTag.get("Items")) { CompoundTag itemData = (CompoundTag) item; // Information about the item From b5f6ada4aea1d5235ec89af508336e092f5ce002 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Tue, 1 Sep 2020 13:30:40 -0400 Subject: [PATCH 009/205] ScoreboardUpdater: Quick fix to lessen CPU usage (#1238) This prevents one/multiple CPU cores from taking up 100% usage. A better, permanent fix will replace this in the coming days. --- .../org/geysermc/connector/scoreboard/ScoreboardUpdater.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/ScoreboardUpdater.java b/connector/src/main/java/org/geysermc/connector/scoreboard/ScoreboardUpdater.java index ee38cecd..3812fb14 100644 --- a/connector/src/main/java/org/geysermc/connector/scoreboard/ScoreboardUpdater.java +++ b/connector/src/main/java/org/geysermc/connector/scoreboard/ScoreboardUpdater.java @@ -31,6 +31,7 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.cache.WorldCache; import org.geysermc.connector.utils.LanguageUtils; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; public class ScoreboardUpdater extends Thread { @@ -61,7 +62,7 @@ public class ScoreboardUpdater extends Thread { @Override public void run() { - while (!session.isClosed()) { + if (!session.isClosed()) { long currentTime = System.currentTimeMillis(); // reset score-packets per second every second @@ -99,6 +100,8 @@ public class ScoreboardUpdater extends Thread { } } } + + session.getConnector().getGeneralThreadPool().schedule(this, 50, TimeUnit.MILLISECONDS); } } From 7c4868cada6d133ad3e25ca2191bea0efd6ba49d Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Tue, 1 Sep 2020 18:29:53 -0400 Subject: [PATCH 010/205] LocaleUtils: don't NPE if no default locale mapping exists (#1239) --- .../main/java/org/geysermc/connector/utils/LocaleUtils.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java index c09af2b7..07c33eb1 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java @@ -246,6 +246,11 @@ public class LocaleUtils { Map localeStrings = LocaleUtils.LOCALE_MAPPINGS.get(locale.toLowerCase()); if (localeStrings == null) localeStrings = LocaleUtils.LOCALE_MAPPINGS.get(LanguageUtils.getDefaultLocale()); + if (localeStrings == null) { + // Don't cause a NPE if the locale is STILL missing + GeyserConnector.getInstance().getLogger().debug("MISSING DEFAULT LOCALE: " + LanguageUtils.getDefaultLocale()); + return messageText; + } return localeStrings.getOrDefault(messageText, messageText); } From d717085c6b1689ad5b305c27d79ab3d9ac7290d3 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Wed, 2 Sep 2020 00:36:53 -0400 Subject: [PATCH 011/205] JaveNotifyClientTranslator: Translate invalid bed message (#1212) This isn't sent as its own message but as a specific event. --- .../translators/java/world/JavaNotifyClientTranslator.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java index 33e602eb..a7bc7b61 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java @@ -43,6 +43,7 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.network.translators.inventory.PlayerInventoryTranslator; +import org.geysermc.connector.utils.LocaleUtils; @Translator(packet = ServerNotifyClientPacket.class) public class JavaNotifyClientTranslator extends PacketTranslator { @@ -141,6 +142,11 @@ public class JavaNotifyClientTranslator extends PacketTranslator Date: Wed, 2 Sep 2020 00:37:24 -0400 Subject: [PATCH 012/205] GeyserSession: Always set Keep Inventory to true (#1213) * GeyserSession: Always set Keep Inventory to true This prevents the client from removing items on death in creative mode if Keep Inventory is true, but doesn't break existing behavior. Essentially, this assures full server-side behavior of the inventory during death. * Small comment update * OK, it was fine before the last commit, but make it better --- .../geysermc/connector/network/session/GeyserSession.java | 5 ++++- 1 file changed, 4 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 3b27fc6a..865ef0f4 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 @@ -301,10 +301,13 @@ public class GeyserSession implements CommandSender { attributesPacket.setAttributes(attributes); upstream.sendPacket(attributesPacket); + GameRulesChangedPacket gamerulePacket = new GameRulesChangedPacket(); // Only allow the server to send health information // Setting this to false allows natural regeneration to work false but doesn't break it being true - GameRulesChangedPacket gamerulePacket = new GameRulesChangedPacket(); gamerulePacket.getGameRules().add(new GameRuleData<>("naturalregeneration", false)); + // Don't let the client modify the inventory on death + // Setting this to true allows keep inventory to work if enabled but doesn't break functionality being false + gamerulePacket.getGameRules().add(new GameRuleData<>("keepinventory", true)); upstream.sendPacket(gamerulePacket); } From b97bf56d99fdeae9e59332f759353aed9645b47c Mon Sep 17 00:00:00 2001 From: Comstepr <32700815+Comstepr@users.noreply.github.com> Date: Wed, 2 Sep 2020 12:37:49 +0800 Subject: [PATCH 013/205] Minor changes to config.yml (#1224) --- connector/src/main/resources/config.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/connector/src/main/resources/config.yml b/connector/src/main/resources/config.yml index bde95d24..7b0bd851 100644 --- a/connector/src/main/resources/config.yml +++ b/connector/src/main/resources/config.yml @@ -13,14 +13,14 @@ bedrock: address: 0.0.0.0 # The port that will listen for connections port: 19132 - # Some hosting services change your Java port everytime you open the server, and require the same port to be used for Bedrock. + # Some hosting services change your Java port everytime you start the server and require the same port to be used for Bedrock. # This option makes the Bedrock port the same as the Java port every time you start the server. # This option is for the plugin version only. clone-remote-port: false - # The MOTD that will be broadcasted to Minecraft: Bedrock Edition clients. Irrelevant if "passthrough-motd" is set to true + # The MOTD that will be broadcasted to Minecraft: Bedrock Edition clients. This is irrelevant if "passthrough-motd" is set to true motd1: "GeyserMC" motd2: "Another GeyserMC forced host." - # The Server Name that will be sent to Minecraft: Bedrock Edition clients. Visible in both the pause menu and the settings menu. + # The Server Name that will be sent to Minecraft: Bedrock Edition clients. This is visible in both the pause menu and the settings menu. server-name: "Geyser" remote: # The IP address of the remote (Java Edition) server @@ -55,7 +55,7 @@ floodgate-key-file: public-key.pem # Disabling this will prevent command suggestions from being sent and solve freezing for Bedrock clients. command-suggestions: true -# The following three options enable "ping passthrough" -the MOTD, player count and/or protocol name gets retrieved from the Java server. +# The following three options enable "ping passthrough" - the MOTD, player count and/or protocol name gets retrieved from the Java server. # Relay the MOTD from the remote server to Bedrock players. passthrough-motd: false # Relay the protocol name (e.g. BungeeCord [X.X], Paper 1.X) - only really useful when using a custom protocol name! From 81a48bf96d0f336c4e2e96e67f77b3162e64ea77 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Wed, 2 Sep 2020 00:38:10 -0400 Subject: [PATCH 014/205] Relocate the rest of our dependencies (#1227) - Relocate all of our dependencies. This does not include MCProtocolLib and Nukkit dependencies at this time as there are no other known plugins that use these dependencies. - Switch to a static commit for Adventure dependencies. Tested working on all versions. --- bootstrap/bungeecord/pom.xml | 16 ++++++++++++++++ bootstrap/spigot/pom.xml | 12 ++++++++++++ bootstrap/sponge/pom.xml | 12 ++++++++++++ bootstrap/velocity/pom.xml | 16 ++++++++++++++++ connector/pom.xml | 12 ++++++------ 5 files changed, 62 insertions(+), 6 deletions(-) diff --git a/bootstrap/bungeecord/pom.xml b/bootstrap/bungeecord/pom.xml index 682690a2..3fb7090f 100644 --- a/bootstrap/bungeecord/pom.xml +++ b/bootstrap/bungeecord/pom.xml @@ -61,6 +61,10 @@ net.md_5.bungee.jni org.geysermc.platform.bungeecord.shaded.jni + + com.fasterxml.jackson + org.geysermc.platform.bungeecord.shaded.jackson + io.netty org.geysermc.platform.bungeecord.shaded.netty @@ -69,6 +73,18 @@ org.reflections org.geysermc.platform.bungeecord.shaded.reflections + + com.google.guava + org.geysermc.platform.bungeecord.shaded.guava + + + org.dom4j + org.geysermc.platform.bungeecord.shaded.dom4j + + + net.kyori.adventure + org.geysermc.platform.bungeecord.shaded.adventure + diff --git a/bootstrap/spigot/pom.xml b/bootstrap/spigot/pom.xml index 8b3ad742..79bdfea0 100644 --- a/bootstrap/spigot/pom.xml +++ b/bootstrap/spigot/pom.xml @@ -79,6 +79,18 @@ org.reflections org.geysermc.platform.spigot.shaded.reflections + + com.google.guava + org.geysermc.platform.spigot.shaded.guava + + + org.dom4j + org.geysermc.platform.spigot.shaded.dom4j + + + net.kyori.adventure + org.geysermc.platform.spigot.shaded.adventure + diff --git a/bootstrap/sponge/pom.xml b/bootstrap/sponge/pom.xml index c21a3a53..3c20239f 100644 --- a/bootstrap/sponge/pom.xml +++ b/bootstrap/sponge/pom.xml @@ -73,6 +73,18 @@ org.reflections org.geysermc.platform.sponge.shaded.reflections + + com.google.guava + org.geysermc.platform.sponge.shaded.guava + + + org.dom4j + org.geysermc.platform.sponge.shaded.dom4j + + + net.kyori.adventure + org.geysermc.platform.sponge.shaded.adventure + diff --git a/bootstrap/velocity/pom.xml b/bootstrap/velocity/pom.xml index 0ebb70ca..efe6c80a 100644 --- a/bootstrap/velocity/pom.xml +++ b/bootstrap/velocity/pom.xml @@ -57,6 +57,10 @@ + + com.fasterxml.jackson + org.geysermc.platform.velocity.shaded.jackson + it.unimi.dsi.fastutil org.geysermc.platform.velocity.shaded.fastutil @@ -65,6 +69,18 @@ org.reflections org.geysermc.platform.velocity.shaded.reflections + + com.google.guava + org.geysermc.platform.velocity.shaded.guava + + + org.dom4j + org.geysermc.platform.velocity.shaded.dom4j + + + net.kyori.adventure + org.geysermc.platform.velocity.shaded.adventure + diff --git a/connector/pom.xml b/connector/pom.xml index 76a83c23..fe35b678 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -137,21 +137,21 @@ 2.1.3 - net.kyori + com.github.kyoripowered.adventure adventure-api - 4.0.0-SNAPSHOT + 557865caef compile - net.kyori + com.github.kyoripowered.adventure adventure-text-serializer-gson - 4.0.0-SNAPSHOT + 557865caef compile - net.kyori + com.github.kyoripowered.adventure adventure-text-serializer-legacy - 4.0.0-SNAPSHOT + 557865caef compile From dcf1731d8adc2f0f6ea176239a71cd7520da3038 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Wed, 2 Sep 2020 00:38:36 -0400 Subject: [PATCH 015/205] Implement correct sign wrapping (#1228) * Implement correct sign wrapping This commit ensures that the auto-wrapping nature of Bedrock with signs is corrected. If a Bedrock player sends a sign that is auto-wrapped, it will now be interpreted by Geyser to fit on multiple lines. Additionally, Geyser will crop incoming sign text to prevent auto-wrapping. * Don't wrap if it's the last line --- .../network/session/GeyserSession.java | 9 ++ .../BedrockBlockEntityDataTranslator.java | 59 +++++++---- .../entity/SignBlockEntityTranslator.java | 17 +++- .../geysermc/connector/utils/SignUtils.java | 99 +++++++++++++++++++ 4 files changed, 163 insertions(+), 21 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/utils/SignUtils.java diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 865ef0f4..27423056 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 @@ -240,6 +240,15 @@ public class GeyserSession implements CommandSender { @Setter private boolean thunder = false; + /** + * 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. + */ + @Setter + private String lastSignMessage; + private MinecraftProtocol protocol; public GeyserSession(GeyserConnector connector, BedrockServerSession bedrockServerSession) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockEntityDataTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockEntityDataTranslator.java index 38b94039..147b8a3b 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockEntityDataTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockEntityDataTranslator.java @@ -32,17 +32,11 @@ import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; - -import java.util.HashMap; -import java.util.Map; +import org.geysermc.connector.utils.SignUtils; @Translator(packet = BlockEntityDataPacket.class) public class BedrockBlockEntityDataTranslator extends PacketTranslator { - // In case two people are editing signs at the same time this array holds the temporary messages to be sent - // Position -> Message being held - protected static Map lastMessages = new HashMap<>(); - @Override public void translate(BlockEntityDataPacket packet, GeyserSession session) { NbtMap tag = packet.getData(); @@ -50,9 +44,8 @@ public class BedrockBlockEntityDataTranslator extends PacketTranslator SignUtils.JAVA_CHARACTER_WIDTH_MAX) { + // We need to apply some more logic if we went over the character width max + boolean wentOverMax = widthCount > SignUtils.JAVA_CHARACTER_WIDTH_MAX && character != '\n'; + widthCount = 0; + // Saves if we're moving a word to the next line + String word = null; + if (wentOverMax && iterator < lines.length - 1) { + // If we went over the max, we want to try to wrap properly like Bedrock does. + // So we look for a space in the Bedrock user's text to imply a word. + int index = newMessage.lastIndexOf(" "); + if (index != -1) { + // There is indeed a space in this line; let's get it + word = newMessage.substring(index + 1); + // 'Delete' that word from the string builder + newMessage.delete(index, newMessage.length()); + } + } lines[iterator] = newMessage.toString(); iterator++; - // Bedrock, for whatever reason, can hold a message out of bounds + // Bedrock, for whatever reason, can hold a message out of the bounds of the four lines // We don't care about that so we discard that if (iterator > lines.length - 1) { break; } newMessage = new StringBuilder(); + if (wentOverMax) { + // Apply the wrapped word to the new line + if (word != null) { + newMessage.append(word); + // And apply the width count + for (char wordCharacter : word.toCharArray()) { + widthCount += SignUtils.getCharacterWidth(wordCharacter); + } + } + // If we went over the max, we want to append the character to the new line. + newMessage.append(character); + widthCount += SignUtils.getCharacterWidth(character); + } } else newMessage.append(character); } // Put the final line on since it isn't done in the for loop if (iterator < lines.length) lines[iterator] = newMessage.toString(); + Position pos = new Position(tag.getInt("x"), tag.getInt("y"), tag.getInt("z")); ClientUpdateSignPacket clientUpdateSignPacket = new ClientUpdateSignPacket(pos, lines); session.sendDownstreamPacket(clientUpdateSignPacket); - //TODO (potentially): originally I was going to update the sign blocks so Bedrock and Java users would match visually - // However Java can still store a lot per-line and visuals are still messed up so that doesn't work - // We remove the sign position from map to indicate there is no work-in-progress sign - lastMessages.remove(pos); + // We set the sign text cached in the session to null to indicate there is no work-in-progress sign + session.setLastSignMessage(null); } } 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 ab9ba306..acec16c5 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 @@ -29,6 +29,7 @@ import com.github.steveice10.mc.protocol.data.message.MessageSerializer; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.nbt.NbtMap; import org.geysermc.connector.utils.MessageUtils; +import org.geysermc.connector.utils.SignUtils; import java.util.HashMap; import java.util.Map; @@ -46,9 +47,17 @@ public class SignBlockEntityTranslator extends BlockEntityTranslator { String signLine = getOrDefault(tag.getValue().get("Text" + currentLine), ""); signLine = MessageUtils.getBedrockMessage(MessageSerializer.fromString(signLine)); - //Java allows up to 16+ characters on certain symbols. - if(signLine.length() >= 15 && (signLine.contains("-") || signLine.contains("="))) { - signLine = signLine.substring(0, 14); + // Check the character width on the sign to ensure there is no overflow that is usually hidden + // to Java Edition clients but will appear to Bedrock clients + int signWidth = 0; + StringBuilder finalSignLine = new StringBuilder(); + for (char c : signLine.toCharArray()) { + signWidth += SignUtils.getCharacterWidth(c); + if (signWidth <= SignUtils.BEDROCK_CHARACTER_WIDTH_MAX) { + finalSignLine.append(c); + } else { + break; + } } // Java Edition 1.14 added the ability to change the text color of the whole sign using dye @@ -56,7 +65,7 @@ public class SignBlockEntityTranslator extends BlockEntityTranslator { signText.append(getBedrockSignColor(tag.get("Color").getValue().toString())); } - signText.append(signLine); + signText.append(finalSignLine.toString()); signText.append("\n"); } diff --git a/connector/src/main/java/org/geysermc/connector/utils/SignUtils.java b/connector/src/main/java/org/geysermc/connector/utils/SignUtils.java new file mode 100644 index 00000000..06406b55 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/utils/SignUtils.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.utils; + +/** + * Provides utilities for interacting with signs. Mainly, it deals with the widths of each character. + * Since Bedrock auto-wraps signs and Java does not, we have to take this into account when translating signs. + */ +public class SignUtils { + + // TODO: If we send the Java font via resource pack, does width change? + /** + * The maximum character width that a sign can hold in Bedrock + */ + public static final int BEDROCK_CHARACTER_WIDTH_MAX = 88; + + /** + * The maximum character width that a sign can hold in Java + */ + public static final int JAVA_CHARACTER_WIDTH_MAX = 90; + + /** + * Gets the Minecraft width of a character + * @param c character to determine + * @return width of the character + */ + public static int getCharacterWidth(char c) { + switch (c) { + case '!': + case ',': + case '.': + case ':': + case ';': + case 'i': + case '|': + case '¡': + return 2; + + case '\'': + case 'l': + case 'ì': + case 'í': + return 3; + + case ' ': + case 'I': + case '[': + case ']': + case 't': + case '×': + case 'ï': + return 4; + + case '"': + case '(': + case ')': + case '*': + case '<': + case '>': + case 'f': + case 'k': + case '{': + case '}': + return 5; + + case '@': + case '~': + case '®': + return 7; + + default: + return 6; + } + } + +} From b21f4773669a304694bc64ed5ed199263f5ab473 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Wed, 2 Sep 2020 00:39:06 -0400 Subject: [PATCH 016/205] Parrot mounting fixes (#1236) * Parrot mounting fixes - Fix duplicate parrots when a parrot leaves the player - Fix rotation of parrots * Remove critical debug information --- .../connector/entity/PlayerEntity.java | 31 ++++++++++++++++--- .../player/BedrockMovePlayerTranslator.java | 7 +++++ 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java index da75812f..58b32e62 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java @@ -43,6 +43,7 @@ import lombok.Getter; import lombok.Setter; import org.geysermc.connector.entity.attribute.Attribute; import org.geysermc.connector.entity.attribute.AttributeType; +import org.geysermc.connector.entity.living.animal.tameable.ParrotEntity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.cache.EntityEffectCache; @@ -65,8 +66,14 @@ public class PlayerEntity extends LivingEntity { private boolean playerList = true; // Player is in the player list private final EntityEffectCache effectCache; - private Entity leftParrot; - private Entity rightParrot; + /** + * Saves the parrot currently on the player's left shoulder; otherwise null + */ + private ParrotEntity leftParrot; + /** + * Saves the parrot currently on the player's right shoulder; otherwise null + */ + private ParrotEntity rightParrot; public PlayerEntity(GameProfile gameProfile, long entityId, long geyserId, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, EntityType.PLAYER, position, motion, rotation); @@ -189,6 +196,12 @@ public class PlayerEntity extends LivingEntity { @Override public void updatePositionAndRotation(GeyserSession session, double moveX, double moveY, double moveZ, float yaw, float pitch, boolean isOnGround) { moveRelative(session, moveX, moveY, moveZ, yaw, pitch, isOnGround); + if (leftParrot != null) { + leftParrot.moveRelative(session, moveX, moveY, moveZ, yaw, pitch, isOnGround); + } + if (rightParrot != null) { + rightParrot.moveRelative(session, moveX, moveY, moveZ, yaw, pitch, isOnGround); + } } @Override @@ -202,6 +215,12 @@ public class PlayerEntity extends LivingEntity { movePlayerPacket.setOnGround(isOnGround); movePlayerPacket.setMode(MovePlayerPacket.Mode.HEAD_ROTATION); session.sendUpstreamPacket(movePlayerPacket); + if (leftParrot != null) { + leftParrot.updateRotation(session, yaw, pitch, isOnGround); + } + if (rightParrot != null) { + rightParrot.updateRotation(session, yaw, pitch, isOnGround); + } } @Override @@ -237,11 +256,15 @@ public class PlayerEntity extends LivingEntity { } // Parrot occupying shoulder - if ((entityMetadata.getId() == 18 && leftParrot == null) || (entityMetadata.getId() == 19 && rightParrot == null)) { // null check since this code just creates the parrot + if (entityMetadata.getId() == 18 || entityMetadata.getId() == 19) { CompoundTag tag = (CompoundTag) entityMetadata.getValue(); if (tag != null && !tag.isEmpty()) { + if ((entityMetadata.getId() == 18 && leftParrot != null) || (entityMetadata.getId() == 19 && rightParrot != null)) { + // No need to update a parrot's data when it already exists + return; + } // The parrot is a separate entity in Bedrock, but part of the player entity in Java - Entity parrot = new Entity(0, session.getEntityCache().getNextEntityId().incrementAndGet(), + ParrotEntity parrot = new ParrotEntity(0, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.PARROT, position, motion, rotation); parrot.spawnEntity(session); parrot.getMetadata().put(EntityData.VARIANT, tag.get("Variant").getValue()); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockMovePlayerTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockMovePlayerTranslator.java index be918ba7..8809941b 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockMovePlayerTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockMovePlayerTranslator.java @@ -86,6 +86,13 @@ public class BedrockMovePlayerTranslator extends PacketTranslator Date: Wed, 2 Sep 2020 00:39:14 -0400 Subject: [PATCH 017/205] Translate scoreboard nametag visibility (#1240) This commit adds support for name tag visibility in teams. If a player is set to hide their nametag, it will be hidden from the Bedrock client. Notably, this fixes most NPC nametag hiding, including Citizens. This does not fix some NPC nametag hiding - there are several NPCs in Hypixel that still have a nametag show up, and they are not a part of any team. --- .../geysermc/connector/entity/PlayerEntity.java | 14 +++++++++++++- .../java/scoreboard/JavaTeamTranslator.java | 2 ++ .../org/geysermc/connector/scoreboard/Team.java | 2 ++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java index 58b32e62..0c269b10 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java @@ -27,6 +27,7 @@ package org.geysermc.connector.entity; import com.github.steveice10.mc.auth.data.GameProfile; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.scoreboard.NameTagVisibility; import com.github.steveice10.mc.protocol.data.message.TextMessage; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.math.vector.Vector3f; @@ -240,7 +241,18 @@ public class PlayerEntity extends LivingEntity { } Team team = session.getWorldCache().getScoreboard().getTeamFor(username); if (team != null) { - metadata.put(EntityData.NAMETAG, team.getPrefix() + MessageUtils.toChatColor(team.getColor()) + username + team.getSuffix()); + // Cover different visibility settings + if (team.getNameTagVisibility() == NameTagVisibility.NEVER) { + metadata.put(EntityData.NAMETAG, ""); + } else if (team.getNameTagVisibility() == NameTagVisibility.HIDE_FOR_OTHER_TEAMS && + !team.getEntities().contains(session.getPlayerEntity().getUsername())) { + metadata.put(EntityData.NAMETAG, ""); + } else if (team.getNameTagVisibility() == NameTagVisibility.HIDE_FOR_OWN_TEAM && + team.getEntities().contains(session.getPlayerEntity().getUsername())) { + metadata.put(EntityData.NAMETAG, ""); + } else { + metadata.put(EntityData.NAMETAG, team.getPrefix() + MessageUtils.toChatColor(team.getColor()) + username + team.getSuffix()); + } } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaTeamTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaTeamTranslator.java index 684245d4..998e5eff 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaTeamTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaTeamTranslator.java @@ -61,6 +61,7 @@ public class JavaTeamTranslator extends PacketTranslator { scoreboard.registerNewTeam(packet.getTeamName(), toPlayerSet(packet.getPlayers())) .setName(MessageUtils.getBedrockMessage(packet.getDisplayName())) .setColor(packet.getColor()) + .setNameTagVisibility(packet.getNameTagVisibility()) .setPrefix(MessageUtils.getTranslatedBedrockMessage(packet.getPrefix(), session.getClientData().getLanguageCode())) .setSuffix(MessageUtils.getTranslatedBedrockMessage(packet.getSuffix(), session.getClientData().getLanguageCode())); break; @@ -75,6 +76,7 @@ public class JavaTeamTranslator extends PacketTranslator { team.setName(MessageUtils.getBedrockMessage(packet.getDisplayName())) .setColor(packet.getColor()) + .setNameTagVisibility(packet.getNameTagVisibility()) .setPrefix(MessageUtils.getTranslatedBedrockMessage(packet.getPrefix(), session.getClientData().getLanguageCode())) .setSuffix(MessageUtils.getTranslatedBedrockMessage(packet.getSuffix(), session.getClientData().getLanguageCode())) .setUpdateType(UpdateType.UPDATE); diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java b/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java index 087ea2dd..a073e2e9 100644 --- a/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java +++ b/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java @@ -25,6 +25,7 @@ package org.geysermc.connector.scoreboard; +import com.github.steveice10.mc.protocol.data.game.scoreboard.NameTagVisibility; import com.github.steveice10.mc.protocol.data.game.scoreboard.TeamColor; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import lombok.Getter; @@ -44,6 +45,7 @@ public class Team { private UpdateType updateType = UpdateType.ADD; private String name; + private NameTagVisibility nameTagVisibility; private String prefix; private TeamColor color; private String suffix; From 4c58568eb411cc4a83bc772148f7f5956de68d9b Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Wed, 2 Sep 2020 23:42:53 -0400 Subject: [PATCH 018/205] Relocate Google Common (#1242) Fixes NoSuchMethodErrors from occuring on certain Spigot servers. --- bootstrap/bungeecord/pom.xml | 6 +++++- bootstrap/spigot/pom.xml | 6 +++++- bootstrap/sponge/pom.xml | 6 +++++- bootstrap/velocity/pom.xml | 6 +++++- 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/bootstrap/bungeecord/pom.xml b/bootstrap/bungeecord/pom.xml index 3fb7090f..44b28e93 100644 --- a/bootstrap/bungeecord/pom.xml +++ b/bootstrap/bungeecord/pom.xml @@ -73,9 +73,13 @@ org.reflections org.geysermc.platform.bungeecord.shaded.reflections + + com.google.common + org.geysermc.platform.bungeecord.shaded.google.common + com.google.guava - org.geysermc.platform.bungeecord.shaded.guava + org.geysermc.platform.bungeecord.shaded.google.guava org.dom4j diff --git a/bootstrap/spigot/pom.xml b/bootstrap/spigot/pom.xml index 79bdfea0..194ec6e5 100644 --- a/bootstrap/spigot/pom.xml +++ b/bootstrap/spigot/pom.xml @@ -79,9 +79,13 @@ org.reflections org.geysermc.platform.spigot.shaded.reflections + + com.google.common + org.geysermc.platform.spigot.shaded.google.common + com.google.guava - org.geysermc.platform.spigot.shaded.guava + org.geysermc.platform.spigot.shaded.google.guava org.dom4j diff --git a/bootstrap/sponge/pom.xml b/bootstrap/sponge/pom.xml index 3c20239f..132f3817 100644 --- a/bootstrap/sponge/pom.xml +++ b/bootstrap/sponge/pom.xml @@ -73,9 +73,13 @@ org.reflections org.geysermc.platform.sponge.shaded.reflections + + com.google.common + org.geysermc.platform.sponge.shaded.google.common + com.google.guava - org.geysermc.platform.sponge.shaded.guava + org.geysermc.platform.sponge.shaded.google.guava org.dom4j diff --git a/bootstrap/velocity/pom.xml b/bootstrap/velocity/pom.xml index efe6c80a..ee445b6e 100644 --- a/bootstrap/velocity/pom.xml +++ b/bootstrap/velocity/pom.xml @@ -69,9 +69,13 @@ org.reflections org.geysermc.platform.velocity.shaded.reflections + + com.google.common + org.geysermc.platform.velocity.shaded.google.common + com.google.guava - org.geysermc.platform.velocity.shaded.guava + org.geysermc.platform.velocity.shaded.google.guava org.dom4j From 5b76a858954ebc2e41058f1ebcf4c4faa28b8589 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Thu, 3 Sep 2020 19:00:36 -0400 Subject: [PATCH 019/205] Non-full-chunk support (#574) This commit adds non-full chunk support if chunk caching is enabled. --- .gitignore | 2 +- bootstrap/spigot/pom.xml | 2 +- .../platform/spigot/GeyserSpigotPlugin.java | 12 +- .../world/GeyserSpigotWorldManager.java | 93 ++++++++++- .../spigot/src/main/resources/biomes.json | 155 ++++++++++++++++++ .../network/session/cache/ChunkCache.java | 11 +- .../java/world/JavaChunkDataTranslator.java | 26 ++- .../translators/world/GeyserWorldManager.java | 8 + .../translators/world/WorldManager.java | 10 ++ .../geysermc/connector/utils/ChunkUtils.java | 18 +- 10 files changed, 320 insertions(+), 17 deletions(-) create mode 100644 bootstrap/spigot/src/main/resources/biomes.json diff --git a/.gitignore b/.gitignore index c4c878af..69c07e50 100644 --- a/.gitignore +++ b/.gitignore @@ -241,4 +241,4 @@ config.yml logs/ public-key.pem locales/ -cache/ \ No newline at end of file +/cache/ \ No newline at end of file diff --git a/bootstrap/spigot/pom.xml b/bootstrap/spigot/pom.xml index 194ec6e5..714c0852 100644 --- a/bootstrap/spigot/pom.xml +++ b/bootstrap/spigot/pom.xml @@ -20,7 +20,7 @@ org.spigotmc spigot-api - 1.14-R0.1-SNAPSHOT + 1.15.2-R0.1-SNAPSHOT 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 d51cf21a..9cc0bc06 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 @@ -56,7 +56,6 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { private GeyserSpigotConfiguration geyserConfig; private GeyserSpigotLogger geyserLogger; private IGeyserPingPassthrough geyserSpigotPingPassthrough; - private GeyserSpigotBlockPlaceListener blockPlaceListener; private GeyserSpigotWorldManager geyserWorldManager; private GeyserConnector connector; @@ -124,10 +123,15 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { // Used to determine if Block.getBlockData() is present. boolean isLegacy = !isCompatible(Bukkit.getServer().getVersion(), "1.13.0"); if (isLegacy) - geyserLogger.debug("Legacy version of Minecraft (1.12.2 or older) detected."); + geyserLogger.debug("Legacy version of Minecraft (1.12.2 or older) detected; falling back to ViaVersion for block state retrieval."); - this.geyserWorldManager = new GeyserSpigotWorldManager(isLegacy, isViaVersion); - this.blockPlaceListener = new GeyserSpigotBlockPlaceListener(connector, isLegacy, isViaVersion); + 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."); + } + + this.geyserWorldManager = new GeyserSpigotWorldManager(isLegacy, use3dBiomes, isViaVersion); + GeyserSpigotBlockPlaceListener blockPlaceListener = new GeyserSpigotBlockPlaceListener(connector, isLegacy, isViaVersion); Bukkit.getServer().getPluginManager().registerEvents(blockPlaceListener, this); diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java index 4873a175..c6443bd0 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java @@ -25,23 +25,71 @@ package org.geysermc.platform.spigot.world; -import lombok.AllArgsConstructor; +import com.fasterxml.jackson.databind.JsonNode; +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.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.GeyserWorldManager; import org.geysermc.connector.network.translators.world.block.BlockTranslator; +import org.geysermc.connector.utils.FileUtils; import org.geysermc.connector.utils.GameRule; +import org.geysermc.connector.utils.LanguageUtils; import us.myles.ViaVersion.protocols.protocol1_13_1to1_13.Protocol1_13_1To1_13; import us.myles.ViaVersion.protocols.protocol1_16_2to1_16_1.data.MappingData; -@AllArgsConstructor +import java.io.InputStream; + public class GeyserSpigotWorldManager extends GeyserWorldManager { private final boolean isLegacy; - // You need ViaVersion to connect to an older server with Geyser. - // However, we still check for ViaVersion in case there's some other way that gets Geyser on a pre-1.13 Bukkit server + private final boolean use3dBiomes; + /** + * You need ViaVersion to connect to an older server with Geyser. + * However, we still check for ViaVersion in case there's some other way that gets Geyser on a pre-1.13 Bukkit server + */ private final boolean isViaVersion; + /** + * Stores a list of {@link Biome} ordinal numbers to Minecraft biome numeric IDs. + * + * 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); + + public GeyserSpigotWorldManager(boolean isLegacy, boolean use3dBiomes, boolean isViaVersion) { + this.isLegacy = isLegacy; + this.use3dBiomes = use3dBiomes; + this.isViaVersion = isViaVersion; + + // 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()) { + if (biomes.has(enumBiome.toString())) { + biomeToIdMap.put(enumBiome.ordinal(), biomes.get(enumBiome.toString()).intValue()); + } else { + GeyserConnector.getInstance().getLogger().debug("No biome mapping found for " + enumBiome.toString() + + ", defaulting to 0"); + biomeToIdMap.put(enumBiome.ordinal(), 0); + } + } + } @Override public int getBlockAt(GeyserSession session, int x, int y, int z) { @@ -77,6 +125,43 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { } @Override + @SuppressWarnings("deprecation") + public int[] getBiomeDataAt(GeyserSession session, int x, int z) { + if (session.getPlayerEntity() == null) { + return new int[1024]; + } + 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; + } + public Boolean getGameRuleBool(GeyserSession session, GameRule gameRule) { return Boolean.parseBoolean(Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld().getGameRuleValue(gameRule.getJavaID())); } diff --git a/bootstrap/spigot/src/main/resources/biomes.json b/bootstrap/spigot/src/main/resources/biomes.json new file mode 100644 index 00000000..56520e91 --- /dev/null +++ b/bootstrap/spigot/src/main/resources/biomes.json @@ -0,0 +1,155 @@ +{ + "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/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 9601a298..2cc9ea13 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,7 +42,7 @@ public class ChunkCache { private final boolean cache; @Getter - private Map chunks = new HashMap<>(); + private final Map chunks = new HashMap<>(); public ChunkCache(GeyserSession session) { if (session.getConnector().getWorldManager().getClass() == GeyserBootstrap.DEFAULT_CHUNK_MANAGER.getClass()) { @@ -57,6 +57,15 @@ public class ChunkCache { return; } ChunkPosition position = new ChunkPosition(chunk.getX(), chunk.getZ()); + if (chunk.getBiomeData() == null && chunks.containsKey(position)) { + Column newColumn = chunk; + chunk = chunks.get(position); + for (int i = 0; i < newColumn.getChunks().length; i++) { + if (newColumn.getChunks()[i] != null) { + chunk.getChunks()[i] = newColumn.getChunks()[i]; + } + } + } chunks.put(position, chunk); } 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 5170929a..6dae9b4d 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,18 +45,33 @@ import org.geysermc.connector.utils.ChunkUtils; @Translator(packet = ServerChunkDataPacket.class) public class JavaChunkDataTranslator extends PacketTranslator { + /** + * Determines if we should process non-full chunks + */ + private final boolean isCacheChunks; + + public JavaChunkDataTranslator() { + isCacheChunks = GeyserConnector.getInstance().getConfig().isCacheChunks(); + } + @Override public void translate(ServerChunkDataPacket packet, GeyserSession session) { if (session.isSpawned()) { ChunkUtils.updateChunkPosition(session, session.getPlayerEntity().getPosition().toInt()); } - if (packet.getColumn().getBiomeData() == null) //Non-full chunk + if (packet.getColumn().getBiomeData() == null && !isCacheChunks) { + // Non-full chunk without chunk caching + session.getConnector().getLogger().debug("Not sending non-full chunk because chunk caching is off."); return; + } + + // Non-full chunks don't have all the chunk data, and Bedrock won't accept that + final boolean isNonFullChunk = (packet.getColumn().getBiomeData() == null); GeyserConnector.getInstance().getGeneralThreadPool().execute(() -> { try { - ChunkUtils.ChunkData chunkData = ChunkUtils.translateToBedrock(packet.getColumn()); + ChunkUtils.ChunkData chunkData = ChunkUtils.translateToBedrock(session, packet.getColumn(), isNonFullChunk); ByteBuf byteBuf = Unpooled.buffer(32); ChunkSection[] sections = chunkData.sections; @@ -71,7 +86,12 @@ public class JavaChunkDataTranslator extends PacketTranslator Date: Fri, 4 Sep 2020 14:08:04 -0400 Subject: [PATCH 020/205] Update mappings to fix 1.16 wood slabs (#1249) --- 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 15958f16..28a22d2b 160000 --- a/connector/src/main/resources/mappings +++ b/connector/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit 15958f16ca36c9536490f8af67f556eba5665dcb +Subproject commit 28a22d2baad680f511bffc36d90d06bf626f0527 From d47360d6fb7b1625b89968deb6e9d829099d099a Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Sat, 5 Sep 2020 16:22:31 -0400 Subject: [PATCH 021/205] GeyserSession: send command permission level OPERATOR if qualified (#1254) Mobile clients have a GUI for commands that shows if CommandPermission.OPERATOR or higher is sent. The commands present all require OP permission 2 or higher; therefore we set that command permission if the server tells us we have a OP permission level of 2 or higher. --- .../geysermc/connector/network/session/GeyserSession.java | 5 ++++- 1 file changed, 4 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 27423056..7a61eb22 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 @@ -710,7 +710,10 @@ public class GeyserSession implements CommandSender { public void sendAdventureSettings() { AdventureSettingsPacket adventureSettingsPacket = new AdventureSettingsPacket(); adventureSettingsPacket.setUniqueEntityId(playerEntity.getGeyserId()); - adventureSettingsPacket.setCommandPermission(CommandPermission.NORMAL); + // Set command permission if OP permission level is high enough + // This allows mobile players access to a GUI for doing commands. The commands there do not change above OPERATOR + // and all commands there are accessible with OP permission level 2 + adventureSettingsPacket.setCommandPermission(opPermissionLevel >= 2 ? CommandPermission.OPERATOR : CommandPermission.NORMAL); adventureSettingsPacket.setPlayerPermission(PlayerPermission.MEMBER); Set flags = new HashSet<>(); From 8c8630814d5f2e238844ade6d3d65c053b47b0c7 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Thu, 10 Sep 2020 11:30:56 -0400 Subject: [PATCH 022/205] Update to 1.16.3 (#1272) * Update for 1.16.3-rc1 * Update to 1.16.3 * Update README * Update MCProtocolLib --- README.md | 2 +- bootstrap/spigot/pom.xml | 2 +- connector/pom.xml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ead5b3b7..156745df 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have now joined us here! -### Currently supporting Minecraft Bedrock v1.16.x and Minecraft Java v1.16.2. +### Currently supporting Minecraft Bedrock v1.16.x and Minecraft Java v1.16.3. ## Setting Up Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set up Geyser. diff --git a/bootstrap/spigot/pom.xml b/bootstrap/spigot/pom.xml index 714c0852..d4dc3326 100644 --- a/bootstrap/spigot/pom.xml +++ b/bootstrap/spigot/pom.xml @@ -26,7 +26,7 @@ us.myles viaversion - 3.1.0 + 3.1.1 provided diff --git a/connector/pom.xml b/connector/pom.xml index fe35b678..5a19a501 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -109,9 +109,9 @@ compile - com.github.steveice10 + com.github.GeyserMC mcprotocollib - b3cf3acbb3 + e4a3aa636a compile From 46c34842d8de76a51cf5ef4fdf1b53369d07a2eb Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Sat, 12 Sep 2020 09:47:43 -0400 Subject: [PATCH 023/205] BedrockInventoryTransactionTranslator: check to make sure bucket usage is on purpose (#1280) Otherwise buckets can be activated when opening block inventories. --- .../bedrock/BedrockInventoryTransactionTranslator.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java index f8e4eb6d..cc1da85e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java @@ -98,7 +98,9 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator Date: Mon, 14 Sep 2020 20:40:41 -0400 Subject: [PATCH 024/205] Check if Fireworks tag is null (#1255) Thank you Mineplex, very cool. --- .../java/org/geysermc/connector/entity/FireworkEntity.java | 4 ++++ 1 file changed, 4 insertions(+) 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 b940b87b..c100c6f3 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/FireworkEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/FireworkEntity.java @@ -72,6 +72,10 @@ public class FireworkEntity extends Entity { } CompoundTag fireworks = tag.get("Fireworks"); + if (fireworks == null) { + // Thank you Mineplex very cool + return; + } NbtMapBuilder fireworksBuilder = NbtMap.builder(); if (fireworks.get("Flight") != null) { From 26802e6dabb796121cb21a95a3e17175b68e959a Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Mon, 14 Sep 2020 20:50:07 -0400 Subject: [PATCH 025/205] Translate CanPlaceOn/CanDestroy NBT (#1253) * Translate CanPlaceOn/CanDestroy NBT This commit adds support for the translation of the CanPlaceOn/CanDestroy NBT for Bedrock clients. * Remove debug line --- .../connector/entity/ItemFrameEntity.java | 9 +---- .../translators/item/ItemRegistry.java | 17 +++++++++ .../translators/item/ItemTranslator.java | 35 +++++++++++++++++++ .../nbt/ShulkerBoxItemTranslator.java | 9 +---- .../world/block/BlockTranslator.java | 34 +++++++++++++----- 5 files changed, 79 insertions(+), 25 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java b/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java index 392cec24..f9d2ace4 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java @@ -34,7 +34,6 @@ import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket; -import com.nukkitx.protocol.bedrock.packet.StartGamePacket; import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; @@ -102,13 +101,7 @@ public class ItemFrameEntity extends Entity { ItemEntry itemEntry = ItemRegistry.getItem((ItemStack) entityMetadata.getValue()); NbtMapBuilder builder = NbtMap.builder(); - String blockName = ""; - for (StartGamePacket.ItemEntry startGamePacketItemEntry : ItemRegistry.ITEMS) { - if (startGamePacketItemEntry.getId() == (short) itemEntry.getBedrockId()) { - blockName = startGamePacketItemEntry.getIdentifier(); - break; - } - } + String blockName = ItemRegistry.getBedrockIdentifer(itemEntry); builder.putByte("Count", (byte) itemData.getCount()); if (itemData.getTag() != null) { 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 56ed2d6e..1f3af019 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 @@ -221,6 +221,23 @@ public class ItemRegistry { .stream().filter(itemEntry -> itemEntry.getJavaIdentifier().equals(key)).findFirst().orElse(null)); } + /** + * Finds the Bedrock string identifier of an ItemEntry + * + * @param entry the ItemEntry to search for + * @return the Bedrock identifier + */ + public static String getBedrockIdentifer(ItemEntry entry) { + String blockName = ""; + for (StartGamePacket.ItemEntry startGamePacketItemEntry : ItemRegistry.ITEMS) { + if (startGamePacketItemEntry.getId() == (short) entry.getBedrockId()) { + blockName = startGamePacketItemEntry.getIdentifier(); // Find the Bedrock string name + break; + } + } + return blockName; + } + /** * Gets a Bedrock {@link ItemData} from a {@link JsonNode} * @param itemNode the JSON node that contains ProxyPass-compatible Bedrock item data diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java index 13725522..864e9fe0 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 @@ -41,6 +41,7 @@ import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.ItemRemapper; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.utils.FileUtils; import org.geysermc.connector.utils.LanguageUtils; import org.geysermc.connector.utils.MessageUtils; @@ -154,9 +155,43 @@ public abstract class ItemTranslator { itemData = DEFAULT_TRANSLATOR.translateToBedrock(itemStack, bedrockItem); } + if (nbt != null) { + // Translate the canDestroy and canPlaceOn Java NBT + ListTag canDestroy = nbt.get("CanDestroy"); + String[] canBreak = new String[0]; + ListTag canPlaceOn = nbt.get("CanPlaceOn"); + String[] canPlace = new String[0]; + canBreak = getCanModify(canDestroy, canBreak); + canPlace = getCanModify(canPlaceOn, canPlace); + itemData = ItemData.of(itemData.getId(), itemData.getDamage(), itemData.getCount(), itemData.getTag(), canPlace, canBreak); + } + return itemData; } + /** + * Translates the Java NBT of canDestroy and canPlaceOn to its Bedrock counterparts. + * In Java, this is treated as normal NBT, but in Bedrock, these arguments are extra parts of the item data itself. + * @param canModifyJava the list of items in Java + * @param canModifyBedrock the empty list of items in Bedrock + * @return the new list of items in Bedrock + */ + private static String[] getCanModify(ListTag canModifyJava, String[] canModifyBedrock) { + if (canModifyJava != null && canModifyJava.size() > 0) { + canModifyBedrock = new String[canModifyJava.size()]; + for (int i = 0; i < canModifyBedrock.length; i++) { + // Get the Java identifier of the block that can be placed + String block = ((StringTag) canModifyJava.get(i)).getValue(); + // Sometimes this is done but it's still valid + if (!block.startsWith("minecraft:")) block = "minecraft:" + block; + // Get the Bedrock identifier of the item and replace it. + // This will unfortunately be limited - for example, beds and banners will be translated weirdly + canModifyBedrock[i] = BlockTranslator.getBedrockBlockIdentifier(block).replace("minecraft:", ""); + } + } + return canModifyBedrock; + } + private static final ItemTranslator DEFAULT_TRANSLATOR = new ItemTranslator() { @Override public List getAppliedItems() { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java index 0645c980..a9930f69 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java @@ -26,7 +26,6 @@ package org.geysermc.connector.network.translators.item.translators.nbt; import com.github.steveice10.opennbt.tag.builtin.*; -import com.nukkitx.protocol.bedrock.packet.StartGamePacket; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.ItemRemapper; import org.geysermc.connector.network.translators.item.ItemEntry; @@ -51,13 +50,7 @@ public class ShulkerBoxItemTranslator extends NbtItemStackTranslator { boxItemTag.put(new ByteTag("WasPickedUp", (byte) 0)); // ??? ItemEntry boxItemEntry = ItemRegistry.getItemEntry(((StringTag) itemData.get("id")).getValue()); - String blockName = ""; - for (StartGamePacket.ItemEntry startGamePacketItemEntry : ItemRegistry.ITEMS) { - if (startGamePacketItemEntry.getId() == (short) boxItemEntry.getBedrockId()) { - blockName = startGamePacketItemEntry.getIdentifier(); // Find the Bedrock string name - break; - } - } + String blockName = ItemRegistry.getBedrockIdentifer(boxItemEntry); boxItemTag.put(new StringTag("Name", blockName)); boxItemTag.put(new ShortTag("Damage", (short) boxItemEntry.getBedrockData())); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java index 8da09f6f..81c86c30 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java @@ -28,15 +28,12 @@ package org.geysermc.connector.network.translators.world.block; import com.fasterxml.jackson.databind.JsonNode; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; -import com.nukkitx.nbt.NBTInputStream; -import com.nukkitx.nbt.NbtList; -import com.nukkitx.nbt.NbtMap; -import com.nukkitx.nbt.NbtMapBuilder; -import com.nukkitx.nbt.NbtType; -import com.nukkitx.nbt.NbtUtils; +import com.nukkitx.nbt.*; import it.unimi.dsi.fastutil.ints.*; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.translators.world.block.entity.BlockEntity; import org.geysermc.connector.utils.FileUtils; @@ -52,6 +49,11 @@ public class BlockTranslator { private static final Int2IntMap JAVA_TO_BEDROCK_BLOCK_MAP = new Int2IntOpenHashMap(); private static final Int2IntMap BEDROCK_TO_JAVA_BLOCK_MAP = new Int2IntOpenHashMap(); + /** + * Stores a list of differences in block identifiers. + * Items will not be added to this list if the key and value is the same. + */ + private static final Object2ObjectMap JAVA_TO_BEDROCK_IDENTIFIERS = new Object2ObjectOpenHashMap<>(); private static final BiMap JAVA_ID_BLOCK_MAP = HashBiMap.create(); private static final IntSet WATERLOGGED = new IntOpenHashSet(); private static final Object2IntMap ITEM_FRAMES = new Object2IntOpenHashMap<>(); @@ -152,11 +154,11 @@ public class BlockTranslator { // Used for adding all "special" Java block states to block state map String identifier; - String bedrock_identifer = entry.getValue().get("bedrock_identifier").asText(); + String bedrockIdentifier = entry.getValue().get("bedrock_identifier").asText(); for (Class clazz : ref.getTypesAnnotatedWith(BlockEntity.class)) { identifier = clazz.getAnnotation(BlockEntity.class).regex(); // Endswith, or else the block bedrock gets picked up for bed - if (bedrock_identifer.endsWith(identifier) && !identifier.equals("")) { + if (bedrockIdentifier.endsWith(identifier) && !identifier.equals("")) { JAVA_ID_TO_BLOCK_ENTITY_MAP.put(javaRuntimeId, clazz.getAnnotation(BlockEntity.class).name()); break; } @@ -164,9 +166,15 @@ public class BlockTranslator { BlockStateValues.storeBlockStateValues(entry, javaRuntimeId); + String cleanJavaIdentifier = entry.getKey().split("\\[")[0]; + + if (!cleanJavaIdentifier.equals(bedrockIdentifier)) { + JAVA_TO_BEDROCK_IDENTIFIERS.put(cleanJavaIdentifier, bedrockIdentifier); + } + // Get the tag needed for non-empty flower pots if (entry.getValue().get("pottable") != null) { - BlockStateValues.getFlowerPotBlocks().put(entry.getKey().split("\\[")[0], buildBedrockState(entry.getValue())); + BlockStateValues.getFlowerPotBlocks().put(cleanJavaIdentifier, buildBedrockState(entry.getValue())); } if ("minecraft:water[level=0]".equals(javaId)) { @@ -297,6 +305,14 @@ public class BlockTranslator { return BEDROCK_TO_JAVA_BLOCK_MAP.get(bedrockId); } + /** + * @param javaIdentifier the Java identifier of the block to search for + * @return the Bedrock identifier if different, or else the Java identifier + */ + public static String getBedrockBlockIdentifier(String javaIdentifier) { + return JAVA_TO_BEDROCK_IDENTIFIERS.getOrDefault(javaIdentifier, javaIdentifier); + } + public static int getItemFrame(NbtMap tag) { return ITEM_FRAMES.getOrDefault(tag, -1); } From b13f5e900f448f7a40f5df27373a5d2d96e22794 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Mon, 14 Sep 2020 20:50:21 -0400 Subject: [PATCH 026/205] PlayerEntity: despawn even if still on the player list (#1263) Fixes LibsDisguises not working, as it uses the same entity ID for the disguised entity and player. The player still appears on the player list. --- .../java/org/geysermc/connector/entity/PlayerEntity.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java index 0c269b10..cc00403a 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java @@ -86,12 +86,6 @@ public class PlayerEntity extends LivingEntity { if (geyserId == 1) valid = true; } - @Override - public boolean despawnEntity(GeyserSession session) { - super.despawnEntity(session); - return !playerList; // don't remove from cache when still on playerlist - } - @Override public void spawnEntity(GeyserSession session) { if (geyserId == 1) return; From 1e1402a23ffd26205a78739210c5d0f122674249 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Mon, 14 Sep 2020 20:51:07 -0400 Subject: [PATCH 027/205] DumpInfo: Mark internal IP as sensitive (#1264) Sometimes the internal IP is the external IP of the server. --- .../org/geysermc/connector/dump/DumpInfo.java | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 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 9d91cde6..8193953a 100644 --- a/connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java +++ b/connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java @@ -31,6 +31,7 @@ import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import lombok.Getter; import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.common.serializer.AsteriskSerializer; import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.network.BedrockProtocol; import org.geysermc.connector.network.session.GeyserSession; @@ -112,16 +113,21 @@ public class DumpInfo { private final boolean dockerCheck; NetworkInfo() { - try { - // This is the most reliable for getting the main local IP - Socket socket = new Socket(); - socket.connect(new InetSocketAddress("geysermc.org", 80)); - this.internalIP = socket.getLocalAddress().getHostAddress(); - } catch (IOException e1) { + if (AsteriskSerializer.showSensitive) { try { - // Fallback to the normal way of getting the local IP - this.internalIP = InetAddress.getLocalHost().getHostAddress(); - } catch (UnknownHostException ignored) { } + // This is the most reliable for getting the main local IP + Socket socket = new Socket(); + socket.connect(new InetSocketAddress("geysermc.org", 80)); + this.internalIP = socket.getLocalAddress().getHostAddress(); + } catch (IOException e1) { + try { + // Fallback to the normal way of getting the local IP + this.internalIP = InetAddress.getLocalHost().getHostAddress(); + } catch (UnknownHostException ignored) { } + } + } else { + // Sometimes the internal IP is the external IP... + this.internalIP = "***"; } this.dockerCheck = DockerCheck.checkBasic(); From 3b274ef9d15d925cb8f2511e06ceb670b89a6484 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Mon, 14 Sep 2020 20:52:16 -0400 Subject: [PATCH 028/205] Pick block improvements (#1265) * Pick block improvements - Creative block picking is now implemented. If the survival-styled block picking fails, then the item is created, following Java-style mechanics. - Entity 'picking' is also implemented. The item is crafted using the same mechanics, and the same rules apply as normal block-picking (except it only works in creative mode, following Java. * Switch some logic around --- .../connector/inventory/PlayerInventory.java | 4 + ...drockBlockPickRequestPacketTranslator.java | 99 --------------- .../BedrockBlockPickRequestTranslator.java | 52 ++++++++ .../BedrockEntityPickRequestTranslator.java | 115 ++++++++++++++++++ .../connector/utils/InventoryUtils.java | 96 ++++++++++++++- 5 files changed, 266 insertions(+), 100 deletions(-) delete mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestPacketTranslator.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestTranslator.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockEntityPickRequestTranslator.java diff --git a/connector/src/main/java/org/geysermc/connector/inventory/PlayerInventory.java b/connector/src/main/java/org/geysermc/connector/inventory/PlayerInventory.java index 432ca827..225335a9 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/PlayerInventory.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/PlayerInventory.java @@ -31,6 +31,10 @@ import lombok.Setter; public class PlayerInventory extends Inventory { + /** + * Stores the held item slot, starting at index 0. + * Add 36 in order to get the network item slot. + */ @Getter @Setter private int heldItemSlot; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestPacketTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestPacketTranslator.java deleted file mode 100644 index 39cfceb1..00000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestPacketTranslator.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.bedrock; - -import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientMoveItemToHotbarPacket; -import com.nukkitx.math.vector.Vector3i; -import com.nukkitx.protocol.bedrock.packet.BlockPickRequestPacket; -import com.nukkitx.protocol.bedrock.packet.PlayerHotbarPacket; -import org.geysermc.connector.inventory.Inventory; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.network.translators.item.ItemEntry; -import org.geysermc.connector.network.translators.item.ItemRegistry; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; - -@Translator(packet = BlockPickRequestPacket.class) -public class BedrockBlockPickRequestPacketTranslator extends PacketTranslator { - - @Override - public void translate(BlockPickRequestPacket packet, GeyserSession session) { - Vector3i vector = packet.getBlockPosition(); - int blockToPick = session.getConnector().getWorldManager().getBlockAt(session, vector.getX(), vector.getY(), vector.getZ()); - - // Block is air - chunk caching is probably off - if (blockToPick == 0) { - return; - } - - // Get the inventory to choose a slot to pick - Inventory inventory = session.getInventoryCache().getOpenInventory(); - if (inventory == null) { - inventory = session.getInventory(); - } - - String targetIdentifier = BlockTranslator.getJavaIdBlockMap().inverse().get(blockToPick).split("\\[")[0]; - - // Check hotbar for item - for (int i = 36; i < 45; i++) { - if (inventory.getItem(i) == null) { - continue; - } - ItemEntry item = ItemRegistry.getItem(inventory.getItem(i)); - // If this isn't the item we're looking for - if (!item.getJavaIdentifier().equals(targetIdentifier)) { - continue; - } - - PlayerHotbarPacket hotbarPacket = new PlayerHotbarPacket(); - hotbarPacket.setContainerId(0); - // Java inventory slot to hotbar slot ID - hotbarPacket.setSelectedHotbarSlot(i - 36); - hotbarPacket.setSelectHotbarSlot(true); - session.sendUpstreamPacket(hotbarPacket); - session.getInventory().setHeldItemSlot(i - 36); - // Don't check inventory if item was in hotbar - return; - } - - // Check inventory for item - for (int i = 9; i < 36; i++) { - if (inventory.getItem(i) == null) { - continue; - } - ItemEntry item = ItemRegistry.getItem(inventory.getItem(i)); - // If this isn't the item we're looking for - if (!item.getJavaIdentifier().equals(targetIdentifier)) { - continue; - } - - ClientMoveItemToHotbarPacket packetToSend = new ClientMoveItemToHotbarPacket(i); // https://wiki.vg/Protocol#Pick_Item - session.sendDownstreamPacket(packetToSend); - return; - } - } -} 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 new file mode 100644 index 00000000..023a83af --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestTranslator.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.bedrock; + +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.protocol.bedrock.packet.BlockPickRequestPacket; +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.world.block.BlockTranslator; +import org.geysermc.connector.utils.InventoryUtils; + +@Translator(packet = BlockPickRequestPacket.class) +public class BedrockBlockPickRequestTranslator extends PacketTranslator { + + @Override + public void translate(BlockPickRequestPacket packet, GeyserSession session) { + Vector3i vector = packet.getBlockPosition(); + int blockToPick = session.getConnector().getWorldManager().getBlockAt(session, vector.getX(), vector.getY(), vector.getZ()); + + // Block is air - chunk caching is probably off + if (blockToPick == 0) { + return; + } + + String targetIdentifier = BlockTranslator.getJavaIdBlockMap().inverse().get(blockToPick).split("\\[")[0]; + InventoryUtils.findOrCreatePickedBlock(session, targetIdentifier); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockEntityPickRequestTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockEntityPickRequestTranslator.java new file mode 100644 index 00000000..4aa044ee --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockEntityPickRequestTranslator.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.bedrock; + +import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.packet.EntityPickRequestPacket; +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 org.geysermc.connector.network.translators.item.ItemEntry; +import org.geysermc.connector.network.translators.item.ItemRegistry; +import org.geysermc.connector.utils.InventoryUtils; + +/** + * Called when the Bedrock user uses the pick block button on an entity + */ +@Translator(packet = EntityPickRequestPacket.class) +public class BedrockEntityPickRequestTranslator extends PacketTranslator { + + @Override + public void translate(EntityPickRequestPacket packet, GeyserSession session) { + if (session.getGameMode() != GameMode.CREATIVE) return; // Apparently Java behavior + Entity entity = session.getEntityCache().getEntityByGeyserId(packet.getRuntimeEntityId()); + if (entity == null) return; + + // Get the corresponding item + String itemName; + switch (entity.getEntityType()) { + case BOAT: + // Include type of boat in the name + int variant = entity.getMetadata().getInt(EntityData.VARIANT); + String typeOfBoat; + switch (variant) { + case 1: + typeOfBoat = "spruce"; + break; + case 2: + typeOfBoat = "birch"; + break; + case 3: + typeOfBoat = "jungle"; + break; + case 4: + typeOfBoat = "acacia"; + break; + case 5: + typeOfBoat = "dark_oak"; + break; + default: + typeOfBoat = "oak"; + break; + } + itemName = typeOfBoat + "_boat"; + break; + case LEASH_KNOT: + itemName = "lead"; + break; + case MINECART_CHEST: + case MINECART_COMMAND_BLOCK: + case MINECART_FURNACE: + case MINECART_HOPPER: + case MINECART_TNT: + // Move MINECART to the end of the name + itemName = entity.getEntityType().toString().toLowerCase().replace("minecart_", "") + "_minecart"; + break; + case MINECART_SPAWNER: + // Turns into a normal minecart + itemName = "minecart"; + break; + case ARMOR_STAND: + case END_CRYSTAL: + case ITEM_FRAME: + case MINECART: + case PAINTING: + // No spawn egg, just an item + itemName = entity.getEntityType().toString().toLowerCase(); + break; + default: + itemName = entity.getEntityType().toString().toLowerCase() + "_spawn_egg"; + break; + } + + String fullItemName = "minecraft:" + itemName; + ItemEntry entry = ItemRegistry.getItemEntry(fullItemName); + // Verify it is, indeed, an item + if (entry == null) return; + + InventoryUtils.findOrCreatePickedBlock(session, fullItemName); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java b/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java index cb51e2f3..b3cc14b9 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java @@ -26,6 +26,10 @@ package org.geysermc.connector.utils; import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; +import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerChangeHeldItemPacket; +import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientCreativeInventoryActionPacket; +import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientMoveItemToHotbarPacket; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtMapBuilder; @@ -33,12 +37,14 @@ import com.nukkitx.nbt.NbtType; import com.nukkitx.protocol.bedrock.data.inventory.ContainerId; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; -import org.geysermc.connector.common.ChatColor; +import com.nukkitx.protocol.bedrock.packet.PlayerHotbarPacket; import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.common.ChatColor; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.inventory.DoubleChestInventoryTranslator; import org.geysermc.connector.network.translators.inventory.InventoryTranslator; +import org.geysermc.connector.network.translators.item.ItemEntry; import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.network.translators.item.ItemTranslator; @@ -151,4 +157,92 @@ public class InventoryUtils { root.put("display", display.build()); return ItemData.of(ItemRegistry.ITEM_ENTRIES.get(ItemRegistry.BARRIER_INDEX).getBedrockId(), (short) 0, 1, root.build()); } + + /** + * Attempt to find the specified item name in the session's inventory. + * If it is found and in the hotbar, set the user's held item to that slot. + * If it is found in another part of the inventory, move it. + * If it is not found and the user is in creative mode, create the item, + * overriding the current item slot if no other hotbar slots are empty, or otherwise selecting the empty slot. + * + * This attempts to mimic Java Edition behavior as best as it can. + * @param session the Bedrock client's session + * @param itemName the Java identifier of the item to search/select + */ + public static void findOrCreatePickedBlock(GeyserSession session, String itemName) { + // Get the inventory to choose a slot to pick + Inventory inventory = session.getInventoryCache().getOpenInventory(); + if (inventory == null) { + inventory = session.getInventory(); + } + + // Check hotbar for item + for (int i = 36; i < 45; i++) { + if (inventory.getItem(i) == null) { + continue; + } + ItemEntry item = ItemRegistry.getItem(inventory.getItem(i)); + // If this isn't the item we're looking for + if (!item.getJavaIdentifier().equals(itemName)) { + continue; + } + + setHotbarItem(session, i); + // Don't check inventory if item was in hotbar + return; + } + + // Check inventory for item + for (int i = 9; i < 36; i++) { + if (inventory.getItem(i) == null) { + continue; + } + ItemEntry item = ItemRegistry.getItem(inventory.getItem(i)); + // If this isn't the item we're looking for + if (!item.getJavaIdentifier().equals(itemName)) { + continue; + } + + ClientMoveItemToHotbarPacket packetToSend = new ClientMoveItemToHotbarPacket(i); // https://wiki.vg/Protocol#Pick_Item + session.sendDownstreamPacket(packetToSend); + return; + } + + // If we still have not found the item, and we're in creative, ask for the item from the server. + if (session.getGameMode() == GameMode.CREATIVE) { + int slot = session.getInventory().getHeldItemSlot() + 36; + if (session.getInventory().getItemInHand() != null) { // Otherwise we should just use the current slot + for (int i = 36; i < 45; i++) { + if (inventory.getItem(i) == null) { + slot = i; + break; + } + } + } + + ClientCreativeInventoryActionPacket actionPacket = new ClientCreativeInventoryActionPacket(slot, + new ItemStack(ItemRegistry.getItemEntry(itemName).getJavaId())); + if ((slot - 36) != session.getInventory().getHeldItemSlot()) { + setHotbarItem(session, slot); + } + session.sendDownstreamPacket(actionPacket); + } + } + + /** + * Changes the held item slot to the specified slot + * @param session GeyserSession + * @param slot inventory slot to be selected + */ + private static void setHotbarItem(GeyserSession session, int slot) { + PlayerHotbarPacket hotbarPacket = new PlayerHotbarPacket(); + hotbarPacket.setContainerId(0); + // Java inventory slot to hotbar slot ID + hotbarPacket.setSelectedHotbarSlot(slot - 36); + hotbarPacket.setSelectHotbarSlot(true); + session.sendUpstreamPacket(hotbarPacket); + ClientPlayerChangeHeldItemPacket heldItemPacket = new ClientPlayerChangeHeldItemPacket(slot); + session.sendDownstreamPacket(heldItemPacket); + session.getInventory().setHeldItemSlot(slot - 36); + } } From 9c8eb00cd59d9272314d2b127bab3c6b5ed02bc8 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Mon, 14 Sep 2020 20:52:50 -0400 Subject: [PATCH 029/205] JavaCollectItemTranslator: check null for entities (#1267) --- .../translators/java/world/JavaCollectItemTranslator.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaCollectItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaCollectItemTranslator.java index 31379bd2..a90c7016 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaCollectItemTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaCollectItemTranslator.java @@ -41,6 +41,7 @@ public class JavaCollectItemTranslator extends PacketTranslator Date: Mon, 14 Sep 2020 20:53:47 -0400 Subject: [PATCH 030/205] Disconnect player if Java sends disconnection (#1274) --- .../java/JavaDisconnectPacket.java | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDisconnectPacket.java diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDisconnectPacket.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDisconnectPacket.java new file mode 100644 index 00000000..43211111 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDisconnectPacket.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.connector.network.translators.java; + +import com.github.steveice10.mc.protocol.packet.ingame.server.ServerDisconnectPacket; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; +import org.geysermc.connector.utils.MessageUtils; + +@Translator(packet = ServerDisconnectPacket.class) +public class JavaDisconnectPacket extends PacketTranslator { + + @Override + public void translate(ServerDisconnectPacket packet, GeyserSession session) { + session.disconnect(MessageUtils.getTranslatedBedrockMessage(packet.getReason(), session.getClientData().getLanguageCode(), true)); + } +} From 6638c53029eca3dc742ec6db08f92d011771cb8a Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Mon, 14 Sep 2020 20:54:19 -0400 Subject: [PATCH 031/205] Implement command block and jigsaw support (#1291) * Implement command block and jigsaw support - Command block UI is now fully implemented to match Java Edition. - Command block minecarts are now supported. - Command blocks now show the correct type of command block. - Jigsaw blocks are translated. Structure blocks can be implemented, but these will be trickier as there are significant GUI differences between Java and Bedrock. * Add more detail about command block minecart color * Set PlayerPermission.OPERATOR to allow command blocks to be destroyed --- README.md | 1 - .../entity/CommandBlockMinecartEntity.java | 67 +++++++++++++++++ .../connector/entity/type/EntityType.java | 2 +- .../network/session/GeyserSession.java | 3 +- .../BedrockBlockEntityDataTranslator.java | 13 ++++ .../BedrockCommandBlockUpdateTranslator.java | 71 +++++++++++++++++++ ...BedrockInventoryTransactionTranslator.java | 33 +++++++++ .../world/JavaUpdateTileEntityTranslator.java | 16 ++++- .../world/block/BlockStateValues.java | 16 +++++ .../world/block/BlockTranslator.java | 33 ++++++--- .../entity/BannerBlockEntityTranslator.java | 16 ----- .../entity/BedBlockEntityTranslator.java | 12 ---- .../block/entity/BlockEntityTranslator.java | 9 +-- .../entity/CampfireBlockEntityTranslator.java | 12 ---- .../CommandBlockBlockEntityTranslator.java | 67 +++++++++++++++++ .../DoubleChestBlockEntityTranslator.java | 10 --- .../entity/EmptyBlockEntityTranslator.java | 10 --- .../EndGatewayBlockEntityTranslator.java | 20 +----- .../JigsawBlockBlockEntityTranslator.java | 47 ++++++++++++ .../ShulkerBoxBlockEntityTranslator.java | 12 ---- .../entity/SignBlockEntityTranslator.java | 18 ----- .../entity/SkullBlockEntityTranslator.java | 13 ---- .../entity/SpawnerBlockEntityTranslator.java | 13 ---- 23 files changed, 359 insertions(+), 155 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/entity/CommandBlockMinecartEntity.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandBlockUpdateTranslator.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CommandBlockBlockEntityTranslator.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/JigsawBlockBlockEntityTranslator.java diff --git a/README.md b/README.md index 156745df..ba3e6fc2 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,6 @@ Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set - [ ] Beacon - [ ] Cartography Table - [ ] Stonecutter - - [ ] Command Block - [ ] Structure Block - [ ] Horse Inventory - [ ] Loom diff --git a/connector/src/main/java/org/geysermc/connector/entity/CommandBlockMinecartEntity.java b/connector/src/main/java/org/geysermc/connector/entity/CommandBlockMinecartEntity.java new file mode 100644 index 00000000..8cabba64 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/CommandBlockMinecartEntity.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.entity; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.message.Message; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; +import org.geysermc.connector.utils.MessageUtils; + +public class CommandBlockMinecartEntity extends DefaultBlockMinecartEntity { + + public CommandBlockMinecartEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + // Required, or else the GUI will not open + metadata.put(EntityData.CONTAINER_TYPE, (byte) 16); + metadata.put(EntityData.CONTAINER_BASE_SIZE, 1); + // Required, or else the client does not bother to send a packet back with the new information + metadata.put(EntityData.COMMAND_BLOCK_ENABLED, (byte) 1); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 13) { + metadata.put(EntityData.COMMAND_BLOCK_COMMAND, entityMetadata.getValue()); + } + if (entityMetadata.getId() == 14) { + metadata.put(EntityData.COMMAND_BLOCK_LAST_OUTPUT, MessageUtils.getBedrockMessage((Message) entityMetadata.getValue())); + } + super.updateBedrockMetadata(entityMetadata, session); + } + + /** + * By default, the command block shown is purple on Bedrock, which does not match Java Edition's orange. + */ + @Override + public void updateDefaultBlockMetadata() { + metadata.put(EntityData.DISPLAY_ITEM, BlockTranslator.BEDROCK_RUNTIME_COMMAND_BLOCK_ID); + metadata.put(EntityData.DISPLAY_OFFSET, 6); + } +} 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 87f4c8b5..500e135e 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 @@ -135,7 +135,7 @@ public enum EntityType { MINECART_CHEST(MinecartEntity.class, 98, 0.7f, 0.98f, 0.98f, 0.35f, "minecraft:chest_minecart"), MINECART_FURNACE(FurnaceMinecartEntity.class, 98, 0.7f, 0.98f, 0.98f, 0.35f, "minecraft:minecart"), MINECART_SPAWNER(SpawnerMinecartEntity.class, 98, 0.7f, 0.98f, 0.98f, 0.35f, "minecraft:minecart"), - MINECART_COMMAND_BLOCK(MinecartEntity.class, 100, 0.7f, 0.98f, 0.98f, 0.35f, "minecraft:command_block_minecart"), + MINECART_COMMAND_BLOCK(CommandBlockMinecartEntity.class, 100, 0.7f, 0.98f, 0.98f, 0.35f, "minecraft:command_block_minecart"), LINGERING_POTION(ThrowableEntity.class, 101, 0f), LLAMA_SPIT(Entity.class, 102, 0.25f), EVOKER_FANGS(Entity.class, 103, 0.8f, 0.5f, 0.5f, 0f, "minecraft:evocation_fang"), 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 7a61eb22..efdb1aa6 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 @@ -714,7 +714,8 @@ public class GeyserSession implements CommandSender { // This allows mobile players access to a GUI for doing commands. The commands there do not change above OPERATOR // and all commands there are accessible with OP permission level 2 adventureSettingsPacket.setCommandPermission(opPermissionLevel >= 2 ? CommandPermission.OPERATOR : CommandPermission.NORMAL); - adventureSettingsPacket.setPlayerPermission(PlayerPermission.MEMBER); + // Required to make command blocks destroyable + adventureSettingsPacket.setPlayerPermission(opPermissionLevel >= 2 ? PlayerPermission.OPERATOR : PlayerPermission.MEMBER); Set flags = new HashSet<>(); if (canFly) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockEntityDataTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockEntityDataTranslator.java index 147b8a3b..3522b4d5 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockEntityDataTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockEntityDataTranslator.java @@ -26,6 +26,7 @@ package org.geysermc.connector.network.translators.bedrock; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; +import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientUpdateJigsawBlockPacket; import com.github.steveice10.mc.protocol.packet.ingame.client.world.ClientUpdateSignPacket; import com.nukkitx.nbt.NbtMap; import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket; @@ -109,6 +110,18 @@ public class BedrockBlockEntityDataTranslator extends PacketTranslator { + + @Override + public void translate(CommandBlockUpdatePacket packet, GeyserSession session) { + String command = packet.getCommand(); + boolean outputTracked = packet.isOutputTracked(); + if (packet.isBlock()) { + CommandBlockMode mode; + switch (packet.getMode()) { + case CHAIN: // The green one + mode = CommandBlockMode.SEQUENCE; + break; + case REPEATING: // The purple one + mode = CommandBlockMode.AUTO; + break; + default: // NORMAL, the orange one + mode = CommandBlockMode.REDSTONE; + break; + } + boolean isConditional = packet.isConditional(); + boolean automatic = !packet.isRedstoneMode(); // Automatic = Always Active option in Java + ClientUpdateCommandBlockPacket commandBlockPacket = new ClientUpdateCommandBlockPacket( + new Position(packet.getBlockPosition().getX(), packet.getBlockPosition().getY(), packet.getBlockPosition().getZ()), + command, mode, outputTracked, isConditional, automatic); + session.sendDownstreamPacket(commandBlockPacket); + } else { + ClientUpdateCommandBlockMinecartPacket commandMinecartPacket = new ClientUpdateCommandBlockMinecartPacket( + (int) session.getEntityCache().getEntityByGeyserId(packet.getMinecartRuntimeEntityId()).getEntityId(), + command, outputTracked + ); + session.sendDownstreamPacket(commandMinecartPacket); + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java index cc1da85e..95e66957 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java @@ -39,8 +39,11 @@ import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlaye import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.LevelEventType; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket; import com.nukkitx.protocol.bedrock.packet.InventoryTransactionPacket; import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; +import org.geysermc.connector.entity.CommandBlockMinecartEntity; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.ItemFrameEntity; import org.geysermc.connector.entity.living.merchant.AbstractMerchantEntity; @@ -105,6 +108,24 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator= 2 && session.getGameMode() == GameMode.CREATIVE) { + // Otherwise insufficient permissions + int blockState = BlockTranslator.getJavaBlockState(packet.getBlockRuntimeId()); + String blockName = BlockTranslator.getJavaIdBlockMap().inverse().getOrDefault(blockState, ""); + // In the future this can be used for structure blocks too, however not all elements + // are available in each GUI + if (blockName.contains("jigsaw")) { + ContainerOpenPacket openPacket = new ContainerOpenPacket(); + openPacket.setBlockPosition(packet.getBlockPosition()); + openPacket.setId((byte) 1); + openPacket.setType(ContainerType.JIGSAW_EDITOR); + openPacket.setUniqueEntityId(-1); + session.sendUpstreamPacket(openPacket); + } + } + } + Vector3i blockPos = packet.getBlockPosition(); // TODO: Find a better way to do this? switch (packet.getBlockFace()) { @@ -197,6 +218,18 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator= 2 && + session.getGameMode() == GameMode.CREATIVE && packet.getNbt().size() > 5) { + ContainerOpenPacket openPacket = new ContainerOpenPacket(); + openPacket.setBlockPosition(Vector3i.from(packet.getPosition().getX(), packet.getPosition().getY(), packet.getPosition().getZ())); + openPacket.setId((byte) 1); + openPacket.setType(ContainerType.COMMAND_BLOCK); + openPacket.setUniqueEntityId(-1); + session.sendUpstreamPacket(openPacket); + } } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java index 53607317..305118e6 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java @@ -39,6 +39,7 @@ public class BlockStateValues { private static final Int2IntMap BANNER_COLORS = new Int2IntOpenHashMap(); private static final Int2ByteMap BED_COLORS = new Int2ByteOpenHashMap(); + private static final Int2ByteMap COMMAND_BLOCK_VALUES = new Int2ByteOpenHashMap(); private static final Int2ObjectMap DOUBLE_CHEST_VALUES = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap FLOWER_POT_VALUES = new Int2ObjectOpenHashMap<>(); private static final Map FLOWER_POT_BLOCKS = new HashMap<>(); @@ -67,6 +68,11 @@ public class BlockStateValues { return; } + if (entry.getKey().contains("command_block")) { + COMMAND_BLOCK_VALUES.put(javaBlockState, entry.getKey().contains("conditional=true") ? (byte) 1 : (byte) 0); + return; + } + if (entry.getValue().get("double_chest_position") != null) { boolean isX = (entry.getValue().get("x") != null); boolean isDirectionPositive = ((entry.getValue().get("x") != null && entry.getValue().get("x").asBoolean()) || @@ -138,6 +144,16 @@ public class BlockStateValues { return -1; } + /** + * The block state in Java and Bedrock both contain the conditional bit, however command block block entity tags + * in Bedrock need the conditional information. + * + * @return the list of all command blocks and if they are conditional (1 or 0) + */ + public static Int2ByteMap getCommandBlockValues() { + return COMMAND_BLOCK_VALUES; + } + /** * All double chest values are part of the block state in Java and part of the block entity tag in Bedrock. * This gives the DoubleChestValue that can be calculated into the final tag. 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 81c86c30..fc7c852c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java @@ -67,6 +67,11 @@ public class BlockTranslator { public static final Int2BooleanMap JAVA_RUNTIME_ID_TO_CAN_HARVEST_WITH_HAND = new Int2BooleanOpenHashMap(); public static final Int2ObjectMap JAVA_RUNTIME_ID_TO_TOOL_TYPE = new Int2ObjectOpenHashMap<>(); + /** + * Runtime command block ID, used for fixing command block minecart appearances + */ + public static final int BEDROCK_RUNTIME_COMMAND_BLOCK_ID; + // For block breaking animation math public static final IntSet JAVA_RUNTIME_WOOL_IDS = new IntOpenHashSet(); public static final int JAVA_RUNTIME_COBWEB_ID; @@ -115,6 +120,7 @@ public class BlockTranslator { int javaRuntimeId = -1; int bedrockRuntimeId = 0; int cobwebRuntimeId = -1; + int commandBlockRuntimeId = -1; int furnaceRuntimeId = -1; int furnaceLitRuntimeId = -1; int spawnerRuntimeId = -1; @@ -142,14 +148,6 @@ public class BlockTranslator { JAVA_RUNTIME_ID_TO_TOOL_TYPE.put(javaRuntimeId, toolTypeNode.textValue()); } - if (javaId.contains("wool")) { - JAVA_RUNTIME_WOOL_IDS.add(javaRuntimeId); - } - - if (javaId.contains("cobweb")) { - cobwebRuntimeId = javaRuntimeId; - } - JAVA_ID_BLOCK_MAP.put(javaId, javaRuntimeId); // Used for adding all "special" Java block states to block state map @@ -205,15 +203,23 @@ public class BlockTranslator { } JAVA_TO_BEDROCK_BLOCK_MAP.put(javaRuntimeId, bedrockRuntimeId); - if (javaId.startsWith("minecraft:furnace[facing=north")) { + if (javaId.contains("wool")) { + JAVA_RUNTIME_WOOL_IDS.add(javaRuntimeId); + + } else if (javaId.contains("cobweb")) { + cobwebRuntimeId = javaRuntimeId; + + } else if (javaId.equals("minecraft:command_block[conditional=false,facing=north]")) { + commandBlockRuntimeId = bedrockRuntimeId; + + } else if (javaId.startsWith("minecraft:furnace[facing=north")) { if (javaId.contains("lit=true")) { furnaceLitRuntimeId = javaRuntimeId; } else { furnaceRuntimeId = javaRuntimeId; } - } - if (javaId.startsWith("minecraft:spawner")) { + } else if (javaId.startsWith("minecraft:spawner")) { spawnerRuntimeId = javaRuntimeId; } @@ -225,6 +231,11 @@ public class BlockTranslator { } JAVA_RUNTIME_COBWEB_ID = cobwebRuntimeId; + if (commandBlockRuntimeId == -1) { + throw new AssertionError("Unable to find command block in palette"); + } + BEDROCK_RUNTIME_COMMAND_BLOCK_ID = commandBlockRuntimeId; + if (furnaceRuntimeId == -1) { throw new AssertionError("Unable to find furnace in palette"); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java index 9e86cb4c..57393a6c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java @@ -27,12 +27,9 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.ListTag; -import com.nukkitx.nbt.NbtMap; -import com.nukkitx.nbt.NbtType; import org.geysermc.connector.network.translators.item.translators.BannerTranslator; import org.geysermc.connector.network.translators.world.block.BlockStateValues; -import java.util.ArrayList; import java.util.HashMap; import java.util.Map; @@ -65,17 +62,4 @@ public class BannerBlockEntityTranslator extends BlockEntityTranslator implement return tags; } - @Override - public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) { - CompoundTag tag = getConstantJavaTag(javaId, x, y, z); - tag.put(new ListTag("Patterns")); - return tag; - } - - @Override - public NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z) { - return getConstantBedrockTag(bedrockId, x, y, z).toBuilder() - .putList("Patterns", NbtType.COMPOUND, new ArrayList<>()) - .build(); - } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedBlockEntityTranslator.java index b84aad98..080bdc3b 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedBlockEntityTranslator.java @@ -26,7 +26,6 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import com.nukkitx.nbt.NbtMap; import org.geysermc.connector.network.translators.world.block.BlockStateValues; import java.util.HashMap; @@ -50,15 +49,4 @@ public class BedBlockEntityTranslator extends BlockEntityTranslator implements R return tags; } - @Override - public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) { - return null; - } - - @Override - public NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z) { - return getConstantBedrockTag(bedrockId, x, y, z).toBuilder() - .putByte("color", (byte) 0) - .build(); - } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java index a538c2dc..54f593a6 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java @@ -28,9 +28,9 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.IntTag; import com.github.steveice10.opennbt.tag.builtin.StringTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtMapBuilder; - import it.unimi.dsi.fastutil.objects.ObjectArrayList; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.utils.BlockEntityUtils; @@ -53,6 +53,7 @@ public abstract class BlockEntityTranslator { { // Bedrock/Java differences put("minecraft:enchanting_table", "EnchantTable"); + put("minecraft:jigsaw", "JigsawBlock"); put("minecraft:piston_head", "PistonArm"); put("minecraft:trapped_chest", "Chest"); // There are some legacy IDs sent but as far as I can tell they are not needed for things to work properly @@ -90,10 +91,6 @@ public abstract class BlockEntityTranslator { public abstract Map translateTag(CompoundTag tag, int blockState); - public abstract CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z); - - public abstract NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z); - public NbtMap getBlockEntityTag(String id, CompoundTag tag, int blockState) { int x = Integer.parseInt(String.valueOf(tag.getValue().get("x").getValue())); int y = Integer.parseInt(String.valueOf(tag.getValue().get("y").getValue())); @@ -124,7 +121,7 @@ public abstract class BlockEntityTranslator { } @SuppressWarnings("unchecked") - protected T getOrDefault(com.github.steveice10.opennbt.tag.builtin.Tag tag, T defaultValue) { + protected T getOrDefault(Tag tag, T defaultValue) { return (tag != null && tag.getValue() != null) ? (T) tag.getValue() : defaultValue; } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CampfireBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CampfireBlockEntityTranslator.java index e3d2c9f5..d6ac0281 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CampfireBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CampfireBlockEntityTranslator.java @@ -50,18 +50,6 @@ public class CampfireBlockEntityTranslator extends BlockEntityTranslator { return tags; } - @Override - public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) { - CompoundTag tag = getConstantJavaTag(javaId, x, y, z); - tag.put(new ListTag("Items")); - return tag; - } - - @Override - public NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z) { - return getConstantBedrockTag(bedrockId, x, y, z); - } - protected NbtMap getItem(CompoundTag tag) { ItemEntry entry = ItemRegistry.getItemEntry((String) tag.get("id").getValue()); NbtMapBuilder tagBuilder = NbtMap.builder() 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 new file mode 100644 index 00000000..6bc940ad --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CommandBlockBlockEntityTranslator.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.world.block.entity; + +import com.github.steveice10.opennbt.tag.builtin.*; +import org.geysermc.connector.network.translators.world.block.BlockStateValues; +import org.geysermc.connector.utils.MessageUtils; + +import java.util.HashMap; +import java.util.Map; + +@BlockEntity(name = "CommandBlock", regex = "command_block") +public class CommandBlockBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { + + @Override + public Map translateTag(CompoundTag tag, int blockState) { + Map map = new HashMap<>(); + if (tag.size() < 5) { + return map; // These values aren't here + } + // Java infers from the block state, but Bedrock needs it in the tag + map.put("conditionalMode", BlockStateValues.getCommandBlockValues().getOrDefault(blockState, (byte) 0)); + // Java and Bedrock values + map.put("conditionMet", ((ByteTag) tag.get("conditionMet")).getValue()); + map.put("auto", ((ByteTag) tag.get("auto")).getValue()); + map.put("CustomName", MessageUtils.getBedrockMessage(((StringTag) tag.get("CustomName")).getValue())); + map.put("powered", ((ByteTag) tag.get("powered")).getValue()); + map.put("Command", ((StringTag) tag.get("Command")).getValue()); + map.put("SuccessCount", ((IntTag) tag.get("SuccessCount")).getValue()); + map.put("TrackOutput", ((ByteTag) tag.get("TrackOutput")).getValue()); + map.put("UpdateLastExecution", ((ByteTag) tag.get("UpdateLastExecution")).getValue()); + if (tag.get("LastExecution") != null) { + map.put("LastExecution", ((LongTag) tag.get("LastExecution")).getValue()); + } else { + map.put("LastExecution", (long) 0); + } + return map; + } + + @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 fa8bab3b..5b59420e 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 @@ -27,7 +27,6 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.math.vector.Vector3i; -import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtMapBuilder; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.block.BlockStateValues; @@ -92,13 +91,4 @@ public class DoubleChestBlockEntityTranslator extends BlockEntityTranslator impl return tags; } - @Override - public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) { - return null; - } - - @Override - public NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z) { - return null; - } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EmptyBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EmptyBlockEntityTranslator.java index 6de13611..e9715bd3 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EmptyBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EmptyBlockEntityTranslator.java @@ -26,7 +26,6 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import com.nukkitx.nbt.NbtMap; import java.util.HashMap; import java.util.Map; @@ -39,13 +38,4 @@ public class EmptyBlockEntityTranslator extends BlockEntityTranslator { return new HashMap<>(); } - @Override - public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) { - return getConstantJavaTag(javaId, x, y, z); - } - - @Override - public NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z) { - return getConstantBedrockTag(bedrockId, x, y, z); - } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EndGatewayBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EndGatewayBlockEntityTranslator.java index 784afed5..af94c560 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EndGatewayBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EndGatewayBlockEntityTranslator.java @@ -26,14 +26,12 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import com.github.steveice10.opennbt.tag.builtin.LongTag; +import com.github.steveice10.opennbt.tag.builtin.IntTag; import com.nukkitx.nbt.NbtList; -import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtType; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; -import java.util.Arrays; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; @@ -56,25 +54,11 @@ public class EndGatewayBlockEntityTranslator extends BlockEntityTranslator { return tags; } - @Override - public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) { - CompoundTag tag = getConstantJavaTag(javaId, x, y, z); - tag.put(new LongTag("Age")); - return tag; - } - - @Override - public NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z) { - return getConstantBedrockTag(bedrockId, x, y, z).toBuilder() - .putList("ExitPortal", NbtType.INT, Arrays.asList(0, 0, 0)) - .build(); - } - private int getExitPortalCoordinate(CompoundTag tag, String axis) { // Return 0 if it doesn't exist, otherwise give proper value if (tag.get("ExitPortal") != null) { LinkedHashMap compoundTag = (LinkedHashMap) tag.get("ExitPortal").getValue(); - com.github.steveice10.opennbt.tag.builtin.IntTag intTag = (com.github.steveice10.opennbt.tag.builtin.IntTag) compoundTag.get(axis); + IntTag intTag = (IntTag) compoundTag.get(axis); return intTag.getValue(); } return 0; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/JigsawBlockBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/JigsawBlockBlockEntityTranslator.java new file mode 100644 index 00000000..43ac1a96 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/JigsawBlockBlockEntityTranslator.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.world.block.entity; + +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.StringTag; + +import java.util.HashMap; +import java.util.Map; + +@BlockEntity(name = "JigsawBlock", regex = "jigsaw") +public class JigsawBlockBlockEntityTranslator extends BlockEntityTranslator { + + @Override + public Map translateTag(CompoundTag tag, int blockState) { + Map map = new HashMap<>(); + map.put("joint", ((StringTag) tag.get("joint")).getValue()); + map.put("name", ((StringTag) tag.get("name")).getValue()); + map.put("target_pool", ((StringTag) tag.get("pool")).getValue()); + map.put("final_state", ((StringTag) tag.get("final_state")).getValue()); + map.put("target", ((StringTag) tag.get("target")).getValue()); + return map; + } +} 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 08a7ae18..08e3abaa 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/ShulkerBoxBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/ShulkerBoxBlockEntityTranslator.java @@ -26,7 +26,6 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import com.nukkitx.nbt.NbtMap; import org.geysermc.connector.network.translators.world.block.BlockStateValues; import java.util.HashMap; @@ -46,15 +45,4 @@ public class ShulkerBoxBlockEntityTranslator extends BlockEntityTranslator { return tags; } - @Override - public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) { - return null; - } - - @Override - public NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z) { - return getConstantBedrockTag(bedrockId, x, y, z).toBuilder() - .putByte("facing", (byte) 1) - .build(); - } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java index acec16c5..b40ed42c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java @@ -27,7 +27,6 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.mc.protocol.data.message.MessageSerializer; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import com.nukkitx.nbt.NbtMap; import org.geysermc.connector.utils.MessageUtils; import org.geysermc.connector.utils.SignUtils; @@ -73,23 +72,6 @@ public class SignBlockEntityTranslator extends BlockEntityTranslator { return tags; } - @Override - public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) { - CompoundTag tag = getConstantJavaTag(javaId, x, y, z); - tag.put(new com.github.steveice10.opennbt.tag.builtin.StringTag("Text1", "{\"text\":\"\"}")); - tag.put(new com.github.steveice10.opennbt.tag.builtin.StringTag("Text2", "{\"text\":\"\"}")); - tag.put(new com.github.steveice10.opennbt.tag.builtin.StringTag("Text3", "{\"text\":\"\"}")); - tag.put(new com.github.steveice10.opennbt.tag.builtin.StringTag("Text4", "{\"text\":\"\"}")); - return tag; - } - - @Override - public NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z) { - return getConstantBedrockTag(bedrockId, x, y, z).toBuilder() - .putString("Text", "") - .build(); - } - /** * Maps a color stored in a sign's Color tag to a Bedrock Edition formatting code. *
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 9547ba2f..6d350c0c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java @@ -25,7 +25,6 @@ package org.geysermc.connector.network.translators.world.block.entity; -import com.nukkitx.nbt.NbtMap; import org.geysermc.connector.network.translators.world.block.BlockStateValues; import java.util.HashMap; @@ -51,16 +50,4 @@ public class SkullBlockEntityTranslator extends BlockEntityTranslator implements return tags; } - @Override - public com.github.steveice10.opennbt.tag.builtin.CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) { - return null; - } - - @Override - public NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z) { - return getConstantBedrockTag(bedrockId, x, y, z).toBuilder() - .putFloat("Rotation", 0f) - .putByte("SkullType", (byte) 0) - .build(); - } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SpawnerBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SpawnerBlockEntityTranslator.java index 3c443eee..2601e3de 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SpawnerBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SpawnerBlockEntityTranslator.java @@ -26,7 +26,6 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import com.nukkitx.nbt.NbtMap; import org.geysermc.connector.entity.type.EntityType; import java.util.HashMap; @@ -86,16 +85,4 @@ public class SpawnerBlockEntityTranslator extends BlockEntityTranslator { return tags; } - @Override - public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) { - return null; - } - - @Override - public NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z) { - return getConstantBedrockTag(bedrockId, x, y, z).toBuilder() - .putByte("isMovable", (byte) 1) - .putString("id", "MobSpawner") - .build(); - } } From f9c1d3f21837f9426db9bf9108faca38e0d6a333 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Wed, 16 Sep 2020 00:11:56 -0400 Subject: [PATCH 032/205] Remove Protocol v409 support (#1300) * Remove Protocol v409 support Protocol has dropped support for this version. * Fix movement * Use a static commit for Protocol --- connector/pom.xml | 6 +++--- .../org/geysermc/connector/network/BedrockProtocol.java | 2 -- .../geysermc/connector/network/session/GeyserSession.java | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/connector/pom.xml b/connector/pom.xml index 5a19a501..f52713ce 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -31,9 +31,9 @@ compile
- com.nukkitx.protocol - bedrock-v409 - 2.6.0-SNAPSHOT + com.github.CloudburstMC.Protocol + bedrock-v408 + 250beb2a94 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 f040fc1f..5d4462b4 100644 --- a/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java +++ b/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java @@ -28,7 +28,6 @@ package org.geysermc.connector.network; import com.nukkitx.protocol.bedrock.BedrockPacketCodec; import com.nukkitx.protocol.bedrock.v407.Bedrock_v407; import com.nukkitx.protocol.bedrock.v408.Bedrock_v408; -import com.nukkitx.protocol.bedrock.v409.Bedrock_v409; import java.util.ArrayList; import java.util.List; @@ -50,7 +49,6 @@ public class BedrockProtocol { static { SUPPORTED_BEDROCK_CODECS.add(Bedrock_v407.V407_CODEC); SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC); - SUPPORTED_BEDROCK_CODECS.add(Bedrock_v409.V409_CODEC); } /** diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index efdb1aa6..81b92282 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 @@ -613,7 +613,7 @@ public class GeyserSession implements CommandSender { startGamePacket.setBlockPalette(BlockTranslator.BLOCKS); startGamePacket.setItemEntries(ItemRegistry.ITEMS); startGamePacket.setVanillaVersion("*"); - // startGamePacket.setMovementServerAuthoritative(true); + startGamePacket.setAuthoritativeMovementMode(AuthoritativeMovementMode.CLIENT); upstream.sendPacket(startGamePacket); } From 3c1d4aae937f445aab50ea62f8e5f02150bba526 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Wed, 16 Sep 2020 00:18:18 -0400 Subject: [PATCH 033/205] Fix inconsistencies with players and the player list (#1298) * Fix inconsistencies with players and the player list This commit makes the player list entry packet control the player cache, fixing inconsistencies that appeared when removing the override on despawning the player. * Update comments --- .../geysermc/connector/entity/PlayerEntity.java | 2 +- .../network/session/cache/EntityCache.java | 3 --- .../player/JavaPlayerListEntryTranslator.java | 15 +++++---------- 3 files changed, 6 insertions(+), 14 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java index cc00403a..390110d1 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java @@ -118,7 +118,7 @@ public class PlayerEntity extends LivingEntity { } public void sendPlayer(GeyserSession session) { - if(session.getEntityCache().getPlayerEntity(uuid) == null) + if (session.getEntityCache().getPlayerEntity(uuid) == null) return; if (session.getUpstream().isInitialized() && session.getEntityCache().getEntityByGeyserId(geyserId) == null) { diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityCache.java index 0bc51ac7..4b54c943 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityCache.java @@ -76,9 +76,6 @@ public class EntityCache { if (entity != null && entity.isValid() && (force || entity.despawnEntity(session))) { long geyserId = entityIdTranslations.remove(entity.getEntityId()); entities.remove(geyserId); - if (entity.is(PlayerEntity.class)) { - playerEntities.remove(entity.as(PlayerEntity.class).getUuid()); - } return true; } return false; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerListEntryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerListEntryTranslator.java index 10b2ba9a..6a9ef4dc 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerListEntryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerListEntryTranslator.java @@ -88,18 +88,13 @@ public class JavaPlayerListEntryTranslator extends PacketTranslator Date: Wed, 16 Sep 2020 16:33:59 +0100 Subject: [PATCH 034/205] [Android] Remove usage of MCProtocolLib Base64 in SkinUtils + more (#1237) * Remove usage of MCProtocolLib Base64 in SkinUtils * Fix path resolution for downloading locales --- .../src/main/java/org/geysermc/connector/utils/LocaleUtils.java | 2 +- .../src/main/java/org/geysermc/connector/utils/SkinUtils.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java index 07c33eb1..d1d59490 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java @@ -133,7 +133,7 @@ public class LocaleUtils { * @param locale Locale to download */ private static void downloadLocale(String locale) { - File localeFile = Paths.get(GeyserConnector.getInstance().getBootstrap().getConfigFolder().toString(),"locales",locale + ".json").toFile(); + File localeFile = GeyserConnector.getInstance().getBootstrap().getConfigFolder().resolve("locales/" + locale + ".json").toFile(); // Check if we have already downloaded the locale file if (localeFile.exists()) { diff --git a/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java b/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java index e3488d6e..fe2a8aa9 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java @@ -270,7 +270,7 @@ public class SkinUtils { GeyserConnector.getInstance().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.skin.bedrock.register", playerEntity.getUsername(), playerEntity.getUuid())); try { - byte[] skinBytes = com.github.steveice10.mc.auth.util.Base64.decode(clientData.getSkinData().getBytes("UTF-8")); + byte[] skinBytes = Base64.getDecoder().decode(clientData.getSkinData().getBytes("UTF-8")); byte[] capeBytes = clientData.getCapeData(); byte[] geometryNameBytes = Base64.getDecoder().decode(clientData.getGeometryName().getBytes("UTF-8")); From 99e72f35b308542cf0dbfb5b58816503c3d6a129 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Thu, 17 Sep 2020 01:08:26 +0100 Subject: [PATCH 035/205] Add support for manually supplying Bedrock resource packs (#1076) * send resource packs A lot of this code is nukkit-credits in the classes * send resource packs A lot of this code is nukkit-credits in the classes * Remove unnecessary code/debugs * use separately generated hashes * Updated mappings and added .mcpack support * "packs" directory auto-create (#484) * "packs" directory auto-create * cleaned indentation in ResourcePack.java * Cleaned ResourcePack.java * Another cleanup I hate editor on github. * Yet another * Another indentation cleanup * Fix resource pack loading (cherry picked from commit f93b07491ea0d4cd96254af47a16f10ce9b92128) * Move back to internal sha256 hashing (cherry picked from commit 812a3d82b2a4d34e67bbccc193fbd87ac9fb174b) * Add resource pack loading back after merge * Add comments, config option and removed unused files * Fix packs folder location and cleanup code * Move to better options for the client * Fix typos in comments * Fix pack loading * Try to make it compile * Final touches? * Add Javadoc for MathUtils#constrain Co-authored-by: EOT3000 <43685885+EOT3000@users.noreply.github.com> Co-authored-by: Vesek <61123478+Vesek@users.noreply.github.com> Co-authored-by: Heath123 Co-authored-by: Camotoy <20743703+DoctorMacc@users.noreply.github.com> --- .gitignore | 3 +- .../geysermc/connector/GeyserConnector.java | 6 +- .../configuration/GeyserConfiguration.java | 2 + .../GeyserJacksonConfiguration.java | 3 + .../network/UpstreamPacketHandler.java | 83 +++++++++++-- .../geysermc/connector/utils/FileUtils.java | 30 +++++ .../geysermc/connector/utils/MathUtils.java | 20 +++ .../connector/utils/ResourcePack.java | 114 +++++++++++++++++ .../connector/utils/ResourcePackManifest.java | 117 ++++++++++++++++++ connector/src/main/resources/config.yml | 5 + 10 files changed, 373 insertions(+), 10 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/utils/ResourcePack.java create mode 100644 connector/src/main/java/org/geysermc/connector/utils/ResourcePackManifest.java diff --git a/.gitignore b/.gitignore index 69c07e50..e3c3b0a5 100644 --- a/.gitignore +++ b/.gitignore @@ -241,4 +241,5 @@ config.yml logs/ public-key.pem locales/ -/cache/ \ No newline at end of file +/cache/ +/packs/ \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 2593c516..3c814393 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -25,6 +25,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.nukkitx.network.raknet.RakNetConstants; @@ -56,6 +57,7 @@ import org.geysermc.connector.network.translators.world.block.entity.BlockEntity import org.geysermc.connector.utils.DimensionUtils; import org.geysermc.connector.utils.LanguageUtils; import org.geysermc.connector.utils.LocaleUtils; +import org.geysermc.connector.utils.ResourcePack; import javax.naming.directory.Attribute; import javax.naming.directory.InitialDirContext; @@ -73,7 +75,7 @@ import java.util.concurrent.TimeUnit; @Getter public class GeyserConnector { - public static final ObjectMapper JSON_MAPPER = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES); + public static final ObjectMapper JSON_MAPPER = new ObjectMapper().enable(JsonParser.Feature.IGNORE_UNDEFINED).enable(JsonParser.Feature.ALLOW_COMMENTS).disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); public static final String NAME = "Geyser"; public static final String VERSION = "DEV"; // A fallback for running in IDEs @@ -136,6 +138,8 @@ public class GeyserConnector { SoundRegistry.init(); SoundHandlerRegistry.init(); + ResourcePack.loadPacks(); + if (platformType != PlatformType.STANDALONE && config.getRemote().getAddress().equals("auto")) { // Set the remote address to localhost since that is where we are always connecting try { 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 2d76e574..c1cc4d03 100644 --- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java +++ b/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java @@ -79,6 +79,8 @@ public interface GeyserConfiguration { boolean isCacheChunks(); + boolean isForceResourcePacks(); + int getCacheImages(); IMetricsInfo getMetrics(); diff --git a/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java b/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java index 6748ef7e..99a3a7a5 100644 --- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java +++ b/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java @@ -103,6 +103,9 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration @JsonProperty("above-bedrock-nether-building") private boolean aboveBedrockNetherBuilding = false; + @JsonProperty("force-resource-packs") + private boolean forceResourcePacks = true; + private MetricsInfo metrics; @Getter 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 f65b3ef1..7e97d429 100644 --- a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java @@ -26,17 +26,24 @@ package org.geysermc.connector.network; import com.nukkitx.protocol.bedrock.BedrockPacket; +import com.nukkitx.protocol.bedrock.data.ResourcePackType; import com.nukkitx.protocol.bedrock.BedrockPacketCodec; import com.nukkitx.protocol.bedrock.packet.*; +import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.common.AuthType; import org.geysermc.connector.configuration.GeyserConfiguration; -import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslatorRegistry; -import org.geysermc.connector.utils.LoginEncryptionUtils; import org.geysermc.connector.utils.LanguageUtils; +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; + public class UpstreamPacketHandler extends LoggingPacketHandler { public UpstreamPacketHandler(GeyserConnector connector, GeyserSession session) { @@ -70,6 +77,11 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { session.sendUpstreamPacket(playStatus); ResourcePacksInfoPacket resourcePacksInfo = new ResourcePacksInfoPacket(); + for(ResourcePack resourcePack : ResourcePack.PACKS.values()) { + ResourcePackManifest.Header header = resourcePack.getManifest().getHeader(); + resourcePacksInfo.getResourcePackInfos().add(new ResourcePacksInfoPacket.Entry(header.getUuid().toString(), header.getVersionString(), resourcePack.getFile().length(), "", "", "", false)); + } + resourcePacksInfo.setForcedToAccept(GeyserConnector.getInstance().getConfig().isForceResourcePacks()); session.sendUpstreamPacket(resourcePacksInfo); return true; } @@ -81,13 +93,42 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { session.connect(connector.getRemoteServer()); connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.connect", session.getAuthData().getName())); break; - case HAVE_ALL_PACKS: - ResourcePackStackPacket stack = new ResourcePackStackPacket(); - stack.setExperimental(false); - stack.setForcedToAccept(false); - stack.setGameVersion("*"); - session.sendUpstreamPacket(stack); + + case SEND_PACKS: + for(String id : packet.getPackIds()) { + ResourcePackDataInfoPacket data = new ResourcePackDataInfoPacket(); + String[] packID = id.split("_"); + ResourcePack pack = ResourcePack.PACKS.get(packID[0]); + ResourcePackManifest.Header header = pack.getManifest().getHeader(); + + data.setPackId(header.getUuid()); + int chunkCount = (int) Math.ceil((int) pack.getFile().length() / (double) ResourcePack.CHUNK_SIZE); + data.setChunkCount(chunkCount); + data.setCompressedPackSize(pack.getFile().length()); + data.setMaxChunkSize(ResourcePack.CHUNK_SIZE); + data.setHash(pack.getSha256()); + data.setPackVersion(packID[1]); + data.setPremium(false); + data.setType(ResourcePackType.RESOURCE); + + session.sendUpstreamPacket(data); + } break; + + case HAVE_ALL_PACKS: + ResourcePackStackPacket stackPacket = new ResourcePackStackPacket(); + stackPacket.setExperimental(false); + stackPacket.setForcedToAccept(false); // Leaving this as false allows the player to choose to download or not + stackPacket.setGameVersion(session.getClientData().getGameVersion()); + + for (ResourcePack pack : ResourcePack.PACKS.values()) { + ResourcePackManifest.Header header = pack.getManifest().getHeader(); + stackPacket.getResourcePacks().add(new ResourcePackStackPacket.Entry(header.getUuid().toString(), header.getVersionString(), "")); + } + + session.sendUpstreamPacket(stackPacket); + break; + default: session.disconnect("disconnectionScreen.resourcePack"); break; @@ -149,4 +190,30 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { boolean defaultHandler(BedrockPacket packet) { return translateAndDefault(packet); } + + @Override + public boolean handle(ResourcePackChunkRequestPacket packet) { + ResourcePackChunkDataPacket data = new ResourcePackChunkDataPacket(); + ResourcePack pack = ResourcePack.PACKS.get(packet.getPackId().toString()); + + data.setChunkIndex(packet.getChunkIndex()); + data.setProgress(packet.getChunkIndex() * ResourcePack.CHUNK_SIZE); + data.setPackVersion(packet.getPackVersion()); + data.setPackId(packet.getPackId()); + + int offset = packet.getChunkIndex() * ResourcePack.CHUNK_SIZE; + byte[] packData = new byte[(int) MathUtils.constrain(pack.getFile().length() - offset, 0, ResourcePack.CHUNK_SIZE)]; + + try (InputStream inputStream = new FileInputStream(pack.getFile())) { + inputStream.skip(offset); + inputStream.read(packData, 0, packData.length); + } catch (Exception e) { + e.printStackTrace(); + } + + data.setData(packData); + + session.sendUpstreamPacket(data); + return true; + } } 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 300946b3..63255cfa 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java @@ -25,6 +25,8 @@ package org.geysermc.connector.utils; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import org.geysermc.connector.GeyserConnector; @@ -37,6 +39,8 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; +import java.nio.file.Files; +import java.security.MessageDigest; import java.util.function.Function; public class FileUtils { @@ -55,6 +59,15 @@ public class FileUtils { return objectMapper.readValue(src, valueType); } + public static T loadYaml(InputStream src, Class valueType) throws IOException { + ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory()).enable(JsonParser.Feature.IGNORE_UNDEFINED).disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + return objectMapper.readValue(src, valueType); + } + + public static T loadJson(InputStream src, Class valueType) throws IOException { + return GeyserConnector.JSON_MAPPER.readValue(src, valueType); + } + /** * Open the specified file or copy if from resources * @@ -145,6 +158,23 @@ public class FileUtils { return stream; } + /** + * Calculate the SHA256 hash of the resource pack file + * @param file File to calculate the hash for + * @return A byte[] representation of the hash + */ + public static byte[] calculateSHA256(File file) { + byte[] sha256; + + try { + sha256 = MessageDigest.getInstance("SHA-256").digest(Files.readAllBytes(file.toPath())); + } catch (Exception e) { + throw new RuntimeException("Could not calculate pack hash", e); + } + + return sha256; + } + /** * Get the stored reflection data for a given path * diff --git a/connector/src/main/java/org/geysermc/connector/utils/MathUtils.java b/connector/src/main/java/org/geysermc/connector/utils/MathUtils.java index 48702492..29dd2cc2 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/MathUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/MathUtils.java @@ -40,6 +40,26 @@ public class MathUtils { return floatNumber > truncated ? truncated + 1 : truncated; } + /** + * If number is greater than the max, set it to max, and if number is lower than low, set it to low. + * @param num number to calculate + * @param min the lowest value the number can be + * @param max the greatest value the number can be + * @return - min if num is lower than min
+ * - max if num is greater than max
+ * - num otherwise + */ + public static double constrain(double num, double min, double max) { + if (num > max) { + num = max; + } + if (num < min) { + num = min; + } + + return num; + } + /** * Converts the given object from an int or byte to byte. * This is used for NBT data that might be either an int diff --git a/connector/src/main/java/org/geysermc/connector/utils/ResourcePack.java b/connector/src/main/java/org/geysermc/connector/utils/ResourcePack.java new file mode 100644 index 00000000..3e9848db --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/utils/ResourcePack.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.utils; + +import org.geysermc.connector.GeyserConnector; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; +import java.util.zip.ZipFile; + +/** + * This represents a resource pack and all the data relevant to it + */ +public class ResourcePack { + /** + * The list of loaded resource packs + */ + public static final Map PACKS = new HashMap<>(); + + /** + * The size of each chunk to use when sending the resource packs to clients in bytes + */ + public static final int CHUNK_SIZE = 102400; + + private byte[] sha256; + private File file; + private ResourcePackManifest manifest; + private ResourcePackManifest.Version version; + + /** + * Loop through the packs directory and locate valid resource pack files + */ + public static void loadPacks() { + File directory = GeyserConnector.getInstance().getBootstrap().getConfigFolder().resolve("packs").toFile(); + + if (!directory.exists()) { + directory.mkdir(); + + // As we just created the directory it will be empty + return; + } + + for (File file : directory.listFiles()) { + if (file.getName().endsWith(".zip") || file.getName().endsWith(".mcpack")) { + ResourcePack pack = new ResourcePack(); + + pack.sha256 = FileUtils.calculateSHA256(file); + + try { + ZipFile zip = new ZipFile(file); + + zip.stream().forEach((x) -> { + if (x.getName().contains("manifest.json")) { + try { + ResourcePackManifest manifest = FileUtils.loadJson(zip.getInputStream(x), ResourcePackManifest.class); + + pack.file = file; + pack.manifest = manifest; + pack.version = ResourcePackManifest.Version.fromArray(manifest.getHeader().getVersion()); + + PACKS.put(pack.getManifest().getHeader().getUuid().toString(), pack); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } catch (Exception e) { + GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.resource_pack.broken", file.getName())); + e.printStackTrace(); + } + } + } + } + + public byte[] getSha256() { + return sha256; + } + + public File getFile() { + return file; + } + + public ResourcePackManifest getManifest() { + return manifest; + } + + public ResourcePackManifest.Version getVersion() { + return version; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/utils/ResourcePackManifest.java b/connector/src/main/java/org/geysermc/connector/utils/ResourcePackManifest.java new file mode 100644 index 00000000..6a08c4db --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/utils/ResourcePackManifest.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.connector.utils; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; +import lombok.Value; + +import java.util.Collection; +import java.util.Collections; +import java.util.UUID; + +/** + * author: NukkitX + * Nukkit Project + */ +@Getter +@EqualsAndHashCode +public class ResourcePackManifest { + @JsonProperty("format_version") + private Integer formatVersion; + private Header header; + private Collection modules; + protected Collection dependencies; + + public Collection getModules() { + return Collections.unmodifiableCollection(modules); + } + + @Getter + @ToString + public static class Header { + private String description; + private String name; + private UUID uuid; + private int[] version; + @JsonProperty("min_engine_version") + private int[] minimumSupportedMinecraftVersion; + + public String getVersionString() { + return version[0] + "." + version[1] + "." + version[2]; + } + } + + @Getter + @ToString + public static class Module { + private String description; + private String name; + private UUID uuid; + private int[] version; + } + + @Getter + @ToString + public static class Dependency { + private UUID uuid; + private int[] version; + } + + @Value + public static class Version { + private final int major; + private final int minor; + private final int patch; + + public static Version fromString(String ver) { + String[] split = ver.replace(']', ' ') + .replace('[', ' ') + .replaceAll(" ", "").split(","); + + return new Version(Integer.parseInt(split[0]), Integer.parseInt(split[1]), Integer.parseInt(split[2])); + } + + public static Version fromArray(int[] ver) { + return new Version(ver[0], ver[1], ver[2]); + } + + private Version(int major, int minor, int patch) { + this.major = major; + this.minor = minor; + this.patch = patch; + } + + + @Override + public String toString() { + return major + "." + minor + "." + patch; + } + } +} + diff --git a/connector/src/main/resources/config.yml b/connector/src/main/resources/config.yml index 7b0bd851..0602bb54 100644 --- a/connector/src/main/resources/config.yml +++ b/connector/src/main/resources/config.yml @@ -112,6 +112,11 @@ cache-images: 0 # the end sky in the nether, but ultimately it's the only way for this feature to work. above-bedrock-nether-building: false +# Force clients to load all resource packs if there are any. +# If set to false it allows the user to disconnect from the server if they don't +# want to download the resource packs +force-resource-packs: true + # bStats is a stat tracker that is entirely anonymous and tracks only basic information # about Geyser, such as how many people are online, how many servers are using Geyser, # what OS is being used, etc. You can learn more about bStats here: https://bstats.org/. From 2f2164f38775442fd909f32267774dcc96d5fc6c Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Thu, 17 Sep 2020 23:07:20 -0400 Subject: [PATCH 036/205] InventoryUtils: Don't send Java packet on hotbar item selection (#1301) The Bedrock client sends a confirmation packet we translate regardless. --- .../java/org/geysermc/connector/utils/InventoryUtils.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java b/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java index b3cc14b9..c1224e6e 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java @@ -27,7 +27,6 @@ package org.geysermc.connector.utils; import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; -import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerChangeHeldItemPacket; import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientCreativeInventoryActionPacket; import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientMoveItemToHotbarPacket; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; @@ -241,8 +240,6 @@ public class InventoryUtils { hotbarPacket.setSelectedHotbarSlot(slot - 36); hotbarPacket.setSelectHotbarSlot(true); session.sendUpstreamPacket(hotbarPacket); - ClientPlayerChangeHeldItemPacket heldItemPacket = new ClientPlayerChangeHeldItemPacket(slot); - session.sendDownstreamPacket(heldItemPacket); - session.getInventory().setHeldItemSlot(slot - 36); + // No need to send a Java packet as Bedrock sends a confirmation packet back that we translate } } From 5fafa0759ef847382785e686c6e7c4a92214845e Mon Sep 17 00:00:00 2001 From: Comstepr <32700815+Comstepr@users.noreply.github.com> Date: Sat, 19 Sep 2020 09:21:44 +0800 Subject: [PATCH 037/205] Update pullrequest.yml (#1305) --- .github/workflows/pullrequest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index 9cb0726c..78a3ce29 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -22,7 +22,7 @@ jobs: - name: submodules-init uses: snickerbockers/submodules-init@v4 - name: Build with Maven - run: mvn -B package + run: mvn -B package -T 2C - name: Archive artifacts (Geyser Standalone) uses: actions/upload-artifact@v2 if: success() From 2db1d16f5c2b923bcc4bf3145e9e6f64a021829a Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Mon, 21 Sep 2020 16:06:25 -0400 Subject: [PATCH 038/205] Only send the client brand on game join (#1299) * Only send the client brand on game join * Apply suggested changes --- .../java/JavaJoinGameTranslator.java | 4 +++ .../PluginMessageUtils.java} | 33 +++++++------------ 2 files changed, 16 insertions(+), 21 deletions(-) rename connector/src/main/java/org/geysermc/connector/{network/translators/java/JavaPluginMessageTranslator.java => utils/PluginMessageUtils.java} (66%) 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 a8fc122b..c479b23f 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 @@ -28,6 +28,7 @@ package org.geysermc.connector.network.translators.java; import com.github.steveice10.mc.protocol.data.game.entity.player.HandPreference; import com.github.steveice10.mc.protocol.data.game.setting.ChatVisibility; import com.github.steveice10.mc.protocol.data.game.setting.SkinPart; +import com.github.steveice10.mc.protocol.packet.ingame.client.ClientPluginMessagePacket; import com.github.steveice10.mc.protocol.packet.ingame.client.ClientSettingsPacket; import com.github.steveice10.mc.protocol.packet.ingame.server.ServerJoinGamePacket; import com.nukkitx.protocol.bedrock.data.GameRuleData; @@ -38,6 +39,7 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.utils.DimensionUtils; +import org.geysermc.connector.utils.PluginMessageUtils; import java.util.Arrays; import java.util.List; @@ -92,6 +94,8 @@ public class JavaJoinGameTranslator extends PacketTranslator { +public class PluginMessageUtils { - private static byte[] brandData; + private static final byte[] BRAND_DATA; static { byte[] data = GeyserConnector.NAME.getBytes(StandardCharsets.UTF_8); byte[] varInt = writeVarInt(data.length); - brandData = new byte[varInt.length + data.length]; - System.arraycopy(varInt, 0, brandData, 0, varInt.length); - System.arraycopy(data, 0, brandData, varInt.length, 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); } - - @Override - public void translate(ServerPluginMessagePacket packet, GeyserSession session) { - if (packet.getChannel().equals("minecraft:brand")) { - session.sendDownstreamPacket( - new ClientPluginMessagePacket(packet.getChannel(), brandData) - ); - } + /** + * Get the prebuilt brand as a byte array + * @return the brand information of the Geyser client + */ + public static byte[] getGeyserBrandData() { + return BRAND_DATA; } private static byte[] writeVarInt(int value) { From 02aeddbadd7c1366c3719b74d22ba52a6920f82c Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Mon, 21 Sep 2020 23:55:13 -0400 Subject: [PATCH 039/205] Scoreboard: Fix various issues (#1286) * Scoreboard: update score on UpdateType.ADD * Actually fix * Readd the Objective when a score changes It looks like Objectives only update when you Remove the Objective and add it back using the SetDisplayObjective. This is hopefully a hotfix, but I think that there is no better way. * Explain score tracking Co-authored-by: Tim203 --- .../geysermc/connector/scoreboard/Objective.java | 2 +- .../geysermc/connector/scoreboard/Scoreboard.java | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/Objective.java b/connector/src/main/java/org/geysermc/connector/scoreboard/Objective.java index 92a1add3..b2f64861 100644 --- a/connector/src/main/java/org/geysermc/connector/scoreboard/Objective.java +++ b/connector/src/main/java/org/geysermc/connector/scoreboard/Objective.java @@ -83,7 +83,7 @@ public class Objective { public void setScore(String id, int score) { if (scores.containsKey(id)) { - scores.get(id).setScore(score).setUpdateType(UpdateType.ADD); + scores.get(id).setScore(score); return; } registerScore(id, score); diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java b/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java index 9f89d9d2..ae1b8275 100644 --- a/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java +++ b/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java @@ -26,7 +26,6 @@ package org.geysermc.connector.scoreboard; import com.github.steveice10.mc.protocol.data.game.scoreboard.ScoreboardPosition; -import com.nukkitx.protocol.bedrock.BedrockPacket; import com.nukkitx.protocol.bedrock.data.ScoreInfo; import com.nukkitx.protocol.bedrock.packet.RemoveObjectivePacket; import com.nukkitx.protocol.bedrock.packet.SetDisplayObjectivePacket; @@ -153,6 +152,10 @@ public class Scoreboard { boolean globalAdd = objective.getUpdateType() == ADD; boolean globalRemove = objective.getUpdateType() == REMOVE; + // Track if any scores changed + // Used to delete and resend scoreboard objectives; otherwise they won't update on Bedrock + boolean scoreChanged = false; + for (Score score : objective.getScores().values()) { Team team = score.getTeam(); @@ -187,6 +190,10 @@ public class Scoreboard { if (remove) { removeScores.add(score.getCachedInfo()); } + + if (add || remove) { + scoreChanged = true; + } // score is pending to be updated, so we use the current score as the old score score.setOldScore(score.getScore()); @@ -198,7 +205,7 @@ public class Scoreboard { score.setUpdateType(NOTHING); } - if (globalRemove || globalUpdate) { + if (globalRemove || globalUpdate || scoreChanged) { RemoveObjectivePacket removeObjectivePacket = new RemoveObjectivePacket(); removeObjectivePacket.setObjectiveId(objective.getObjectiveName()); session.sendUpstreamPacket(removeObjectivePacket); @@ -208,7 +215,7 @@ public class Scoreboard { } } - if (globalAdd || globalUpdate) { + if (globalAdd || globalUpdate || scoreChanged) { SetDisplayObjectivePacket displayObjectivePacket = new SetDisplayObjectivePacket(); displayObjectivePacket.setObjectiveId(objective.getObjectiveName()); displayObjectivePacket.setDisplayName(objective.getDisplayName()); From 2dc7dc10ff11b9435920d8a2188a57298dca9f2c Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Tue, 22 Sep 2020 14:15:59 -0400 Subject: [PATCH 040/205] Remove warning about slot 50 (#1325) We know that it occurs with console crafting. --- .../translators/inventory/CraftingInventoryTranslator.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/CraftingInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/CraftingInventoryTranslator.java index f5ebe62d..b260565b 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/CraftingInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/CraftingInventoryTranslator.java @@ -30,12 +30,9 @@ import com.nukkitx.protocol.bedrock.data.inventory.ContainerId; import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData; import com.nukkitx.protocol.bedrock.data.inventory.InventorySource; -import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket; -import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.inventory.updater.CursorInventoryUpdater; -import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater; import org.geysermc.connector.utils.InventoryUtils; import java.util.List; @@ -48,7 +45,7 @@ public class CraftingInventoryTranslator extends BlockInventoryTranslator { @Override public int bedrockSlotToJava(InventoryActionData action) { if (action.getSlot() == 50) { - GeyserConnector.getInstance().getLogger().warning("Slot 50 found, please report: " + action); + // Slot 50 is used for crafting with a controller. return 0; } From b4c7682130d4e8931da2f5dfa10d984eb2e17f9d Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Tue, 22 Sep 2020 14:16:57 -0400 Subject: [PATCH 041/205] Implement experience sounds (#1320) Bedrock sends a level event for the experience sound around the same time as a Java entity collect item packet is sent. --- .../JavaEntityCollectItemTranslator.java} | 35 ++++++++++++++----- 1 file changed, 26 insertions(+), 9 deletions(-) rename connector/src/main/java/org/geysermc/connector/network/translators/java/{world/JavaCollectItemTranslator.java => entity/JavaEntityCollectItemTranslator.java} (61%) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaCollectItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityCollectItemTranslator.java similarity index 61% rename from connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaCollectItemTranslator.java rename to connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityCollectItemTranslator.java index a90c7016..270c33a7 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaCollectItemTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityCollectItemTranslator.java @@ -23,26 +23,32 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.java.world; +package org.geysermc.connector.network.translators.java.entity; import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityCollectItemPacket; +import com.nukkitx.protocol.bedrock.data.LevelEventType; +import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; import com.nukkitx.protocol.bedrock.packet.TakeItemEntityPacket; import org.geysermc.connector.entity.Entity; +import org.geysermc.connector.entity.ExpOrbEntity; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; +/** + * This packet is called whenever a player picks up an item. + * In Java, this is called for item entities, experience orbs and arrows + * Bedrock uses it for arrows and item entities, but not experience orbs. + */ @Translator(packet = ServerEntityCollectItemPacket.class) -public class JavaCollectItemTranslator extends PacketTranslator { +public class JavaEntityCollectItemTranslator extends PacketTranslator { @Override public void translate(ServerEntityCollectItemPacket packet, GeyserSession session) { - // This is the definition of translating - both packets take the same values - TakeItemEntityPacket takeItemEntityPacket = new TakeItemEntityPacket(); - // Collected entity is the item + // Collected entity is the other entity Entity collectedEntity = session.getEntityCache().getEntityByJavaId(packet.getCollectedEntityId()); if (collectedEntity == null) return; - // Collector is the entity picking up the item + // Collector is the entity 'picking up' the item Entity collectorEntity; if (packet.getCollectorEntityId() == session.getPlayerEntity().getEntityId()) { collectorEntity = session.getPlayerEntity(); @@ -50,8 +56,19 @@ public class JavaCollectItemTranslator extends PacketTranslator Date: Tue, 22 Sep 2020 20:10:38 -0500 Subject: [PATCH 042/205] Use SERVER_READY in BedrockRespawnTranslator to fix respawn bug --- .../network/translators/bedrock/BedrockRespawnTranslator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockRespawnTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockRespawnTranslator.java index 4e4b4a30..71d52e13 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockRespawnTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockRespawnTranslator.java @@ -43,7 +43,7 @@ public class BedrockRespawnTranslator extends PacketTranslator { RespawnPacket respawnPacket = new RespawnPacket(); respawnPacket.setRuntimeEntityId(0); respawnPacket.setPosition(Vector3f.ZERO); - respawnPacket.setState(RespawnPacket.State.SERVER_SEARCHING); + respawnPacket.setState(RespawnPacket.State.SERVER_READY); session.sendUpstreamPacket(respawnPacket); } From 1ec768d95d313525e03af4b500927c29a2eb5762 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Thu, 24 Sep 2020 12:54:18 -0400 Subject: [PATCH 043/205] Fix interaction spam bug (#1324) * Fix interaction spam bug This references the Nukkit 1.0 fix for the client bug of spamming to interact. Holding down still works. * Remove interaction position set at action type 1 * Remove debug line --- .../connector/network/session/GeyserSession.java | 14 +++++++++++++- .../BedrockInventoryTransactionTranslator.java | 14 +++++++++++--- 2 files changed, 24 insertions(+), 4 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 81b92282..0a28b11f 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 @@ -156,8 +156,13 @@ public class GeyserSession implements CommandSender { @Setter private boolean interacting; + /** + * Stores the last position of the block the player interacted with. This can either be a block that the client + * placed or an existing block the player interacted with (for example, a chest).
+ * Initialized as (0, 0, 0) so it is always not-null. + */ @Setter - private Vector3i lastInteractionPosition; + private Vector3i lastInteractionPosition = Vector3i.ZERO; private boolean manyDimPackets = false; private ServerRespawnPacket lastDimPacket = null; @@ -193,6 +198,13 @@ public class GeyserSession implements CommandSender { @Setter private long lastHitTime; + /** + * Store the last time the player interacted. Used to fix a right-click spam bug. + * See https://github.com/GeyserMC/Geyser/issues/503 for context. + */ + @Setter + private long lastInteractionTime; + private boolean reducedDebugInfo = false; @Setter diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java index 95e66957..131fdcc9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java @@ -78,6 +78,17 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator Date: Thu, 24 Sep 2020 15:11:42 -0400 Subject: [PATCH 044/205] Fix picking up liquids with buckets (#1311) * Fix picking up liquids with buckets The last fix to prevent bucket placement upon interacting with an inventory had an oversight with empty buckets, making them unusable. This commit fixes that while keeping the previous fix. * Remove debug line * Fix milk drinking and visual bucket item apperance * Comment elaboration * Make indentiation better --- ...BedrockInventoryTransactionTranslator.java | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java index 131fdcc9..8e891d92 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java @@ -39,8 +39,10 @@ import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlaye import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.LevelEventType; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerId; import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket; +import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; import com.nukkitx.protocol.bedrock.packet.InventoryTransactionPacket; import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; import org.geysermc.connector.entity.CommandBlockMinecartEntity; @@ -112,11 +114,23 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator Date: Fri, 25 Sep 2020 15:09:02 -0400 Subject: [PATCH 045/205] Prevent Bedrock from changing gamemode client-side (#1337) In vanilla Bedrock, if you have operator status, the client sends a packet to change gamemode without confirmation from the server. Since we have a custom server option to request the gamemode, we just reset the gamemode and ignore this packet. --- .../BedrockSetPlayerGameTypeTranslator.java | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockSetPlayerGameTypeTranslator.java diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockSetPlayerGameTypeTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockSetPlayerGameTypeTranslator.java new file mode 100644 index 00000000..d61b3786 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockSetPlayerGameTypeTranslator.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.bedrock.entity.player; + +import com.nukkitx.protocol.bedrock.packet.SetPlayerGameTypePacket; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; + +/** + * In vanilla Bedrock, if you have operator status, this sets the player's gamemode without confirmation from the server. + * Since we have a custom server option to request the gamemode, we just reset the gamemode and ignore this. + */ +@Translator(packet = SetPlayerGameTypePacket.class) +public class BedrockSetPlayerGameTypeTranslator extends PacketTranslator { + + @Override + public void translate(SetPlayerGameTypePacket packet, GeyserSession session) { + // no + SetPlayerGameTypePacket playerGameTypePacket = new SetPlayerGameTypePacket(); + playerGameTypePacket.setGamemode(session.getGameMode().ordinal()); + session.sendUpstreamPacket(playerGameTypePacket); + } +} From d9b05f5b72beed41d0a57d0594de6c88b47b649d Mon Sep 17 00:00:00 2001 From: Luke <32024335+lukeeey@users.noreply.github.com> Date: Fri, 25 Sep 2020 20:52:44 +0100 Subject: [PATCH 046/205] Update the CI link in the README (#1339) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ba3e6fc2..e0404a93 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![forthebadge made-with-java](http://ForTheBadge.com/images/badges/made-with-java.svg)](https://java.com/) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) -[![Build Status](https://ci.nukkitx.com/job/Geyser/job/master/badge/icon)](https://ci.nukkitx.com/job/Geyser/job/master/) +[![Build Status](https://ci.nukkitx.com/job/Geyser/job/master/badge/icon)](https://ci.nukkitx.com/job/GeyserMC/job/Geyser/job/master/) [![Discord](https://img.shields.io/discord/613163671870242838.svg?color=%237289da&label=discord)](http://discord.geysermc.org/) [![HitCount](http://hits.dwyl.io/Geyser/GeyserMC.svg)](http://hits.dwyl.io/Geyser/GeyserMC) [![Crowdin](https://badges.crowdin.net/geyser/localized.svg)](https://translate.geysermc.org/) From 7c49391b9d38373bbe0164032c8d01b41929a8c7 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Mon, 28 Sep 2020 22:43:50 +0100 Subject: [PATCH 047/205] Fix gamemodes not fully applying on server switch (#1348) * Fix gamemodes not fully applying on server switch * Revert previous commit and move session flag updating to the adventure settings method --- .../org/geysermc/connector/network/session/GeyserSession.java | 4 ++++ .../translators/java/world/JavaNotifyClientTranslator.java | 2 -- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 0a28b11f..300877f8 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 @@ -729,6 +729,10 @@ public class GeyserSession implements CommandSender { // Required to make command blocks destroyable adventureSettingsPacket.setPlayerPermission(opPermissionLevel >= 2 ? PlayerPermission.OPERATOR : PlayerPermission.MEMBER); + // Update the noClip and worldImmutable values based on the current gamemode + noClip = gameMode == GameMode.SPECTATOR; + worldImmutable = gameMode == GameMode.ADVENTURE || gameMode == GameMode.SPECTATOR; + Set flags = new HashSet<>(); if (canFly) { flags.add(AdventureSetting.MAY_FLY); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java index a7bc7b61..493a7ca1 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java @@ -103,8 +103,6 @@ public class JavaNotifyClientTranslator extends PacketTranslator Date: Tue, 29 Sep 2020 01:09:57 +0300 Subject: [PATCH 048/205] Update README.md (#1349) Make the server address look cool (for build 405, sorry) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e0404a93..f7201bb0 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set - Download: http://ci.geysermc.org - Discord: http://discord.geysermc.org/ - ~~Donate: https://patreon.com/GeyserMC~~ Currently disabled. -- Test Server: test.geysermc.org port 25565 for Java and 19132 for Bedrock +- Test Server: `test.geysermc.org` port `25565` for Java and `19132` for Bedrock ## What's Left to be Added/Fixed - The Following Inventories From 650c02ef669c3a525c66810fbd834ad191991c47 Mon Sep 17 00:00:00 2001 From: bundabrg Date: Tue, 29 Sep 2020 11:49:46 +0800 Subject: [PATCH 049/205] Remove 'geyser' from parameters when executing a command under Spigot, Bungeecord, Sponge, Velocity (#1266) * Remove 'geyser' from parameters when executing a command under Spigot, Bungeecode, Sponge, Velocity Fixes https://github.com/bundabrg/GeyserReversion/issues/8 * Fix case when there are no sub commands Co-authored-by: bundabrg --- .../bungeecord/command/GeyserBungeeCommandExecutor.java | 4 ++-- .../spigot/command/GeyserSpigotCommandExecutor.java | 4 ++-- .../sponge/command/GeyserSpongeCommandExecutor.java | 4 ++-- .../velocity/command/GeyserVelocityCommandExecutor.java | 6 ++++-- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/GeyserBungeeCommandExecutor.java b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/GeyserBungeeCommandExecutor.java index 3b051c5c..f673a3f5 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/GeyserBungeeCommandExecutor.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/GeyserBungeeCommandExecutor.java @@ -64,10 +64,10 @@ public class GeyserBungeeCommandExecutor extends Command implements TabExecutor sender.sendMessage(TextComponent.fromLegacyText(ChatColor.RED + message)); return; } - getCommand(args[0]).execute(new BungeeCommandSender(sender), args); + getCommand(args[0]).execute(new BungeeCommandSender(sender), args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]); } } else { - getCommand("help").execute(new BungeeCommandSender(sender), args); + getCommand("help").execute(new BungeeCommandSender(sender), new String[0]); } } diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/GeyserSpigotCommandExecutor.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/GeyserSpigotCommandExecutor.java index 38187275..2dba2901 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/GeyserSpigotCommandExecutor.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/GeyserSpigotCommandExecutor.java @@ -59,11 +59,11 @@ public class GeyserSpigotCommandExecutor implements TabExecutor { sender.sendMessage(ChatColor.RED + message); return true; } - getCommand(args[0]).execute(new SpigotCommandSender(sender), args); + getCommand(args[0]).execute(new SpigotCommandSender(sender), args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]); return true; } } else { - getCommand("help").execute(new SpigotCommandSender(sender), args); + getCommand("help").execute(new SpigotCommandSender(sender), new String[0]); return true; } return true; diff --git a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/GeyserSpongeCommandExecutor.java b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/GeyserSpongeCommandExecutor.java index d37321ff..c77e8271 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/GeyserSpongeCommandExecutor.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/GeyserSpongeCommandExecutor.java @@ -59,10 +59,10 @@ public class GeyserSpongeCommandExecutor implements CommandCallable { source.sendMessage(Text.of(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.bootstrap.command.permission_fail"))); return CommandResult.success(); } - getCommand(args[0]).execute(new SpongeCommandSender(source), args); + getCommand(args[0]).execute(new SpongeCommandSender(source), args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]); } } else { - getCommand("help").execute(new SpongeCommandSender(source), args); + getCommand("help").execute(new SpongeCommandSender(source), new String[0]); } return CommandResult.success(); } diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandExecutor.java b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandExecutor.java index fa3aaa3c..afd6c3bf 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandExecutor.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandExecutor.java @@ -37,6 +37,8 @@ import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.GeyserCommand; import org.geysermc.connector.utils.LanguageUtils; +import java.util.Arrays; + @AllArgsConstructor public class GeyserVelocityCommandExecutor implements Command { @@ -51,10 +53,10 @@ public class GeyserVelocityCommandExecutor implements Command { source.sendMessage(TextComponent.of(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.bootstrap.command.permission_fail"))); return; } - getCommand(args[0]).execute(new VelocityCommandSender(source), args); + getCommand(args[0]).execute(new VelocityCommandSender(source), args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]); } } else { - getCommand("help").execute(new VelocityCommandSender(source), args); + getCommand("help").execute(new VelocityCommandSender(source), new String[0]); } } From a5b00e09a1111d9f0999989ff773dc1d405e2786 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Tue, 29 Sep 2020 13:19:37 -0400 Subject: [PATCH 050/205] Villager trade fixes (#1350) This commit mainly focuses on fixing the crashing of villagers that occurred pre-1.14. Co-authored-by: AJ Ferguson --- .../holder/BlockInventoryHolder.java | 5 ++--- .../java/window/JavaOpenWindowTranslator.java | 4 +++- .../java/world/JavaTradeListTranslator.java | 20 +++++++++++++------ 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java index 6dfde5d1..6afdb25d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java @@ -35,9 +35,8 @@ import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; import lombok.AllArgsConstructor; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.network.translators.inventory.InventoryTranslator; -import org.geysermc.connector.utils.LocaleUtils; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; @AllArgsConstructor public class BlockInventoryHolder extends InventoryHolder { @@ -60,7 +59,7 @@ public class BlockInventoryHolder extends InventoryHolder { .putInt("x", position.getX()) .putInt("y", position.getY()) .putInt("z", position.getZ()) - .putString("CustomName", LocaleUtils.getLocaleString(inventory.getTitle(), session.getClientData().getLanguageCode())).build(); + .putString("CustomName", inventory.getTitle()).build(); BlockEntityDataPacket dataPacket = new BlockEntityDataPacket(); dataPacket.setData(tag); dataPacket.setBlockPosition(position); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java index dde45cf8..820639b3 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java @@ -29,7 +29,6 @@ import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientClose import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerOpenWindowPacket; import com.google.gson.JsonObject; import com.google.gson.JsonParser; -import com.nukkitx.protocol.bedrock.packet.ContainerClosePacket; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; @@ -37,6 +36,7 @@ import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.network.translators.inventory.InventoryTranslator; import org.geysermc.connector.utils.InventoryUtils; +import org.geysermc.connector.utils.LocaleUtils; @Translator(packet = ServerOpenWindowPacket.class) public class JavaOpenWindowTranslator extends PacketTranslator { @@ -71,6 +71,8 @@ public class JavaOpenWindowTranslator extends PacketTranslator 0 ? packet.getVillagerLevel() - 1 : 0); // -1 crashes client recipe.put("buyA", getItemTag(session, trade.getFirstInput(), trade.getSpecialPrice())); if (trade.getSecondInput() != null) { recipe.put("buyB", getItemTag(session, trade.getSecondInput(), 0)); From 9bb52afc8a8ca4ebb89e7e7fc39a8a678bb56244 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Tue, 29 Sep 2020 13:21:25 -0400 Subject: [PATCH 051/205] BedrockRespawnTranslator: prevent some respawn bugs (#1346) --- .../bedrock/BedrockRespawnTranslator.java | 33 +++++++++++++++---- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockRespawnTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockRespawnTranslator.java index 71d52e13..dc98c363 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockRespawnTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockRespawnTranslator.java @@ -28,7 +28,10 @@ package org.geysermc.connector.network.translators.bedrock; import com.github.steveice10.mc.protocol.data.game.ClientRequest; import com.github.steveice10.mc.protocol.packet.ingame.client.ClientRequestPacket; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket; import com.nukkitx.protocol.bedrock.packet.RespawnPacket; +import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket; +import org.geysermc.connector.entity.PlayerEntity; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; @@ -39,12 +42,30 @@ public class BedrockRespawnTranslator extends PacketTranslator { @Override public void translate(RespawnPacket packet, GeyserSession session) { if (packet.getState() == RespawnPacket.State.CLIENT_READY) { - if (!session.isSpawned()) { // Otherwise when immediate respawn is on the client never loads - RespawnPacket respawnPacket = new RespawnPacket(); - respawnPacket.setRuntimeEntityId(0); - respawnPacket.setPosition(Vector3f.ZERO); - respawnPacket.setState(RespawnPacket.State.SERVER_READY); - session.sendUpstreamPacket(respawnPacket); + // Previously we only sent the respawn packet before the server finished loading + // The message included was 'Otherwise when immediate respawn is on the client never loads' + // But I assume the new if statement below fixes that problem + RespawnPacket respawnPacket = new RespawnPacket(); + respawnPacket.setRuntimeEntityId(0); + respawnPacket.setPosition(Vector3f.ZERO); + respawnPacket.setState(RespawnPacket.State.SERVER_READY); + session.sendUpstreamPacket(respawnPacket); + + if (session.isSpawned()) { + // Client might be stuck; resend spawn information + PlayerEntity entity = session.getPlayerEntity(); + if (entity == null) return; + SetEntityDataPacket entityDataPacket = new SetEntityDataPacket(); + entityDataPacket.setRuntimeEntityId(entity.getGeyserId()); + entityDataPacket.getMetadata().putAll(entity.getMetadata()); + session.sendUpstreamPacket(entityDataPacket); + + MovePlayerPacket movePlayerPacket = new MovePlayerPacket(); + movePlayerPacket.setRuntimeEntityId(entity.getGeyserId()); + movePlayerPacket.setPosition(entity.getPosition()); + movePlayerPacket.setRotation(entity.getBedrockRotation()); + movePlayerPacket.setMode(MovePlayerPacket.Mode.RESPAWN); + session.sendUpstreamPacket(movePlayerPacket); } ClientRequestPacket javaRespawnPacket = new ClientRequestPacket(ClientRequest.RESPAWN); From aee9ccc7d2ca8ecc932ae92dcab38161e1653bc4 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Tue, 29 Sep 2020 13:21:43 -0400 Subject: [PATCH 052/205] DoorSoundInteractionHandler: ignore iron [trap]doors (#1343) --- .../translators/sound/block/DoorSoundInteractionHandler.java | 1 + 1 file changed, 1 insertion(+) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/sound/block/DoorSoundInteractionHandler.java b/connector/src/main/java/org/geysermc/connector/network/translators/sound/block/DoorSoundInteractionHandler.java index 10d4bb89..a1df72d0 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/sound/block/DoorSoundInteractionHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/sound/block/DoorSoundInteractionHandler.java @@ -37,6 +37,7 @@ public class DoorSoundInteractionHandler implements BlockSoundInteractionHandler @Override public void handleInteraction(GeyserSession session, Vector3f position, String identifier) { + if (identifier.contains("iron")) return; LevelEventPacket levelEventPacket = new LevelEventPacket(); levelEventPacket.setType(LevelEventType.SOUND_DOOR_OPEN); levelEventPacket.setPosition(position); From 3c4cde96779f490f302aa420513e03bcad1b9dd6 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Tue, 29 Sep 2020 13:30:37 -0400 Subject: [PATCH 053/205] Tipped arrow translation (#1331) * Tipped arrow translation - Tipped arrow items are now properly translated both ways - Tipped arrow particle effects are also translated, by having a list of all colors Java could send us and their Bedrock ID * Remove a whitespace --- .../connector/entity/TippedArrowEntity.java | 27 ++++ .../translators/item/ItemRegistry.java | 4 +- .../network/translators/item/Potion.java | 2 +- .../translators/item/TippedArrowPotion.java | 151 ++++++++++++++++++ .../item/translators/BannerTranslator.java | 2 +- .../item/translators/CompassTranslator.java | 2 +- .../item/translators/PotionTranslator.java | 4 +- .../translators/TippedArrowTranslator.java | 87 ++++++++++ 8 files changed, 273 insertions(+), 6 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/item/TippedArrowPotion.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/item/translators/TippedArrowTranslator.java 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 def5715c..949764b9 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/TippedArrowEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/TippedArrowEntity.java @@ -25,12 +25,39 @@ package org.geysermc.connector.entity; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.item.TippedArrowPotion; +/** + * Internally this is known as TippedArrowEntity but is used with tipped arrows and normal arrows + */ public class TippedArrowEntity extends AbstractArrowEntity { public TippedArrowEntity(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) { + // Arrow potion effect color + if (entityMetadata.getId() == 9) { + int potionColor = (int) entityMetadata.getValue(); + // -1 means no color + if (potionColor == -1) { + metadata.remove(EntityData.CUSTOM_DISPLAY); + } 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); + } + } + } + super.updateBedrockMetadata(entityMetadata, session); + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java index 1f3af019..850e4e05 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 @@ -197,7 +197,9 @@ public class ItemRegistry { */ public static ItemEntry getItem(ItemData data) { for (ItemEntry itemEntry : ITEM_ENTRIES.values()) { - if (itemEntry.getBedrockId() == data.getId() && (itemEntry.getBedrockData() == data.getDamage() || itemEntry.getJavaIdentifier().endsWith("potion"))) { + if (itemEntry.getBedrockId() == data.getId() && (itemEntry.getBedrockData() == data.getDamage() || + // Make exceptions for potions and tipped arrows, whose damage values can vary + (itemEntry.getJavaIdentifier().endsWith("potion") || itemEntry.getJavaIdentifier().equals("minecraft:arrow")))) { return itemEntry; } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/Potion.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/Potion.java index 51ae36e4..b9a213d8 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/Potion.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/Potion.java @@ -48,7 +48,7 @@ public enum Potion { STRONG_SWIFTNESS(16), LONG_SWIFTNESS(15), SLOWNESS(17), - STRONG_SLOWNESS(18), //does not exist + STRONG_SLOWNESS(42), LONG_SLOWNESS(18), WATER_BREATHING(19), LONG_WATER_BREATHING(20), diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/TippedArrowPotion.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/TippedArrowPotion.java new file mode 100644 index 00000000..7a5b576b --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/TippedArrowPotion.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.item; + +import lombok.Getter; + +import java.util.Locale; + +/** + * Potion identifiers and their respective Bedrock IDs used with arrows. + * https://minecraft.gamepedia.com/Arrow#Item_Data + */ +@Getter +public enum TippedArrowPotion { + MUNDANE(2, ArrowParticleColors.NONE), // 3 is extended? + THICK(4, ArrowParticleColors.NONE), + AWKWARD(5, ArrowParticleColors.NONE), + NIGHT_VISION(6, ArrowParticleColors.NIGHT_VISION), + LONG_NIGHT_VISION(7, ArrowParticleColors.NIGHT_VISION), + INVISIBILITY(8, ArrowParticleColors.INVISIBILITY), + LONG_INVISIBILITY(9, ArrowParticleColors.INVISIBILITY), + LEAPING(10, ArrowParticleColors.LEAPING), + LONG_LEAPING(11, ArrowParticleColors.LEAPING), + STRONG_LEAPING(12, ArrowParticleColors.LEAPING), + FIRE_RESISTANCE(13, ArrowParticleColors.FIRE_RESISTANCE), + LONG_FIRE_RESISTANCE(14, ArrowParticleColors.FIRE_RESISTANCE), + SWIFTNESS(15, ArrowParticleColors.SWIFTNESS), + LONG_SWIFTNESS(16, ArrowParticleColors.SWIFTNESS), + STRONG_SWIFTNESS(17, ArrowParticleColors.SWIFTNESS), + SLOWNESS(18, ArrowParticleColors.SLOWNESS), + LONG_SLOWNESS(19, ArrowParticleColors.SLOWNESS), + STRONG_SLOWNESS(43, ArrowParticleColors.SLOWNESS), + WATER_BREATHING(20, ArrowParticleColors.WATER_BREATHING), + LONG_WATER_BREATHING(21, ArrowParticleColors.WATER_BREATHING), + HEALING(22, ArrowParticleColors.HEALING), + STRONG_HEALING(23, ArrowParticleColors.HEALING), + HARMING(24, ArrowParticleColors.HARMING), + STRONG_HARMING(25, ArrowParticleColors.HARMING), + POISON(26, ArrowParticleColors.POISON), + LONG_POISON(27, ArrowParticleColors.POISON), + STRONG_POISON(28, ArrowParticleColors.POISON), + REGENERATION(29, ArrowParticleColors.REGENERATION), + LONG_REGENERATION(30, ArrowParticleColors.REGENERATION), + STRONG_REGENERATION(31, ArrowParticleColors.REGENERATION), + STRENGTH(32, ArrowParticleColors.STRENGTH), + LONG_STRENGTH(33, ArrowParticleColors.STRENGTH), + STRONG_STRENGTH(34, ArrowParticleColors.STRENGTH), + WEAKNESS(35, ArrowParticleColors.WEAKNESS), + LONG_WEAKNESS(36, ArrowParticleColors.WEAKNESS), + LUCK(2, ArrowParticleColors.NONE), // does not exist in Bedrock + TURTLE_MASTER(38, ArrowParticleColors.TURTLE_MASTER), + LONG_TURTLE_MASTER(39, ArrowParticleColors.TURTLE_MASTER), + STRONG_TURTLE_MASTER(40, ArrowParticleColors.TURTLE_MASTER), + SLOW_FALLING(41, ArrowParticleColors.SLOW_FALLING), + LONG_SLOW_FALLING(42, ArrowParticleColors.SLOW_FALLING); + + private final String javaIdentifier; + private final short bedrockId; + /** + * The Java color associated with this ID. + * Used for looking up Java arrow color entity metadata as Bedrock potion IDs, which is what is used for entities in Bedrock + */ + private final int javaColor; + + TippedArrowPotion(int bedrockId, ArrowParticleColors arrowParticleColor) { + this.javaIdentifier = "minecraft:" + this.name().toLowerCase(Locale.ENGLISH); + this.bedrockId = (short) bedrockId; + this.javaColor = arrowParticleColor.getColor(); + } + + public static TippedArrowPotion getByJavaIdentifier(String javaIdentifier) { + for (TippedArrowPotion potion : TippedArrowPotion.values()) { + if (potion.javaIdentifier.equals(javaIdentifier)) { + return potion; + } + } + return null; + } + + public static TippedArrowPotion getByBedrockId(short bedrockId) { + for (TippedArrowPotion potion : TippedArrowPotion.values()) { + if (potion.bedrockId == bedrockId) { + return potion; + } + } + return null; + } + + /** + * @param color the potion color to look up + * @return the tipped arrow potion that most closely resembles that color. + */ + public static TippedArrowPotion getByJavaColor(int color) { + for (TippedArrowPotion potion : TippedArrowPotion.values()) { + if (potion.javaColor == color) { + return potion; + } + } + return null; + } + + private enum ArrowParticleColors { + NONE(-1), + NIGHT_VISION(2039713), + INVISIBILITY(8356754), + LEAPING(2293580), + FIRE_RESISTANCE(14981690), + SWIFTNESS(8171462), + SLOWNESS(5926017), + TURTLE_MASTER(7691106), + WATER_BREATHING(3035801), + HEALING(16262179), + HARMING(4393481), + POISON(5149489), + REGENERATION(13458603), + STRENGTH(9643043), + WEAKNESS(4738376), + LUCK(3381504), + SLOW_FALLING(16773073); + + @Getter + private final int color; + + ArrowParticleColors(int color) { + this.color = color; + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java index 304ea3fb..f4bfdfb6 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java @@ -50,7 +50,7 @@ import java.util.stream.Collectors; @ItemRemapper public class BannerTranslator extends ItemTranslator { - private List appliedItems; + private final List appliedItems; public BannerTranslator() { appliedItems = ItemRegistry.ITEM_ENTRIES.values().stream().filter(entry -> entry.getJavaIdentifier().endsWith("banner")).collect(Collectors.toList()); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/CompassTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/CompassTranslator.java index 675d4255..159b9ab4 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/CompassTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/CompassTranslator.java @@ -40,7 +40,7 @@ import java.util.stream.Collectors; @ItemRemapper public class CompassTranslator extends ItemTranslator { - private List appliedItems; + private final List appliedItems; public CompassTranslator() { appliedItems = ItemRegistry.ITEM_ENTRIES.values().stream().filter(entry -> entry.getJavaIdentifier().endsWith("compass")).collect(Collectors.toList()); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/PotionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/PotionTranslator.java index 7cb88d70..24130a7f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/PotionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/PotionTranslator.java @@ -42,7 +42,7 @@ import java.util.stream.Collectors; @ItemRemapper public class PotionTranslator extends ItemTranslator { - private List appliedItems; + private final List appliedItems; public PotionTranslator() { appliedItems = ItemRegistry.ITEM_ENTRIES.values().stream().filter(entry -> entry.getJavaIdentifier().endsWith("potion")).collect(Collectors.toList()); @@ -57,7 +57,7 @@ public class PotionTranslator extends ItemTranslator { if (potion != null) { return ItemData.of(itemEntry.getBedrockId(), potion.getBedrockId(), itemStack.getAmount(), translateNbtToBedrock(itemStack.getNbt())); } - GeyserConnector.getInstance().getLogger().debug("Unknown java potion: " + potionTag.getValue()); + GeyserConnector.getInstance().getLogger().debug("Unknown Java potion: " + potionTag.getValue()); } return super.translateToBedrock(itemStack, itemEntry); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/TippedArrowTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/TippedArrowTranslator.java new file mode 100644 index 00000000..0b69d6a2 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/TippedArrowTranslator.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.item.translators; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.github.steveice10.opennbt.tag.builtin.StringTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; +import com.nukkitx.protocol.bedrock.data.inventory.ItemData; +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.network.translators.ItemRemapper; +import org.geysermc.connector.network.translators.item.ItemEntry; +import org.geysermc.connector.network.translators.item.ItemRegistry; +import org.geysermc.connector.network.translators.item.ItemTranslator; +import org.geysermc.connector.network.translators.item.TippedArrowPotion; + +import java.util.List; +import java.util.stream.Collectors; + +@ItemRemapper +public class TippedArrowTranslator extends ItemTranslator { + + private final List appliedItems; + + private static final int TIPPED_ARROW_JAVA_ID = ItemRegistry.getItemEntry("minecraft:tipped_arrow").getJavaId(); + + public TippedArrowTranslator() { + appliedItems = ItemRegistry.ITEM_ENTRIES.values().stream().filter(entry -> + entry.getJavaIdentifier().contains("arrow") && !entry.getJavaIdentifier().contains("spectral")).collect(Collectors.toList()); + } + + @Override + public ItemData translateToBedrock(ItemStack itemStack, ItemEntry itemEntry) { + if (!itemEntry.getJavaIdentifier().equals("minecraft:tipped_arrow") || itemStack.getNbt() == null) { + // We're only concerned about minecraft:arrow when translating Bedrock -> Java + return super.translateToBedrock(itemStack, itemEntry); + } + Tag potionTag = itemStack.getNbt().get("Potion"); + if (potionTag instanceof StringTag) { + TippedArrowPotion tippedArrowPotion = TippedArrowPotion.getByJavaIdentifier(((StringTag) potionTag).getValue()); + if (tippedArrowPotion != null) { + return ItemData.of(itemEntry.getBedrockId(), tippedArrowPotion.getBedrockId(), itemStack.getAmount(), translateNbtToBedrock(itemStack.getNbt())); + } + GeyserConnector.getInstance().getLogger().debug("Unknown Java potion (tipped arrow): " + potionTag.getValue()); + } + return super.translateToBedrock(itemStack, itemEntry); + } + + @Override + public ItemStack translateToJava(ItemData itemData, ItemEntry itemEntry) { + TippedArrowPotion tippedArrowPotion = TippedArrowPotion.getByBedrockId(itemData.getDamage()); + ItemStack itemStack = super.translateToJava(itemData, itemEntry); + if (tippedArrowPotion != null) { + itemStack = new ItemStack(TIPPED_ARROW_JAVA_ID, itemStack.getAmount(), itemStack.getNbt()); + StringTag potionTag = new StringTag("Potion", tippedArrowPotion.getJavaIdentifier()); + itemStack.getNbt().put(potionTag); + } + return itemStack; + } + + @Override + public List getAppliedItems() { + return appliedItems; + } +} From 772cb246f02bc6349d9a95f14fa62bcc9e2be0d2 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Tue, 29 Sep 2020 14:15:11 -0400 Subject: [PATCH 054/205] Forward keep alive packets to the client (#1344) * Forward keep alive packets to the client Previously, MCProtocolLib (our Java protocol library) handled keep alive packets for us. This commit disables that option and 'forwards' the keep alive packets to the client, and sending the keep alive packet back once Bedrock sends us a ping response. * Delete DataCache * Update to latest MCProtocolLib * Swap values around as a sanity check --- connector/pom.xml | 4 +- .../network/session/GeyserSession.java | 13 +++-- .../translators/PacketTranslatorRegistry.java | 2 - ...BedrockNetworkStackLatencyTranslator.java} | 25 ++++++---- .../java/JavaKeepAliveTranslator.java | 48 +++++++++++++++++++ 5 files changed, 76 insertions(+), 16 deletions(-) rename connector/src/main/java/org/geysermc/connector/network/{session/cache/DataCache.java => translators/bedrock/BedrockNetworkStackLatencyTranslator.java} (54%) create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/java/JavaKeepAliveTranslator.java diff --git a/connector/pom.xml b/connector/pom.xml index f52713ce..26ec0c4e 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -109,9 +109,9 @@ compile
- com.github.GeyserMC + com.github.steveice10 mcprotocollib - e4a3aa636a + 976c2d0f89 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 300877f8..5225632c 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 @@ -28,6 +28,7 @@ package org.geysermc.connector.network.session; import com.github.steveice10.mc.auth.data.GameProfile; import com.github.steveice10.mc.auth.exception.request.InvalidCredentialsException; import com.github.steveice10.mc.auth.exception.request.RequestException; +import com.github.steveice10.mc.protocol.MinecraftConstants; import com.github.steveice10.mc.protocol.MinecraftProtocol; import com.github.steveice10.mc.protocol.data.SubProtocol; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; @@ -117,8 +118,6 @@ public class GeyserSession implements CommandSender { */ private final Object2LongMap itemFrameCache = new Object2LongOpenHashMap<>(); - private DataCache javaPacketCache; - @Setter private Vector2i lastChunkPosition = null; private int renderDistance; @@ -176,6 +175,12 @@ public class GeyserSession implements CommandSender { @Setter private long lastWindowCloseTime = 0; + /** + * Saves the timestamp of the last keep alive packet + */ + @Setter + private long lastKeepAliveTimestamp = 0; + @Setter private VillagerTrade[] villagerTrades; @Setter @@ -276,8 +281,6 @@ public class GeyserSession implements CommandSender { this.playerEntity = new PlayerEntity(new GameProfile(UUID.randomUUID(), "unknown"), 1, 1, Vector3f.ZERO, Vector3f.ZERO, Vector3f.ZERO); this.inventory = new PlayerInventory(); - this.javaPacketCache = new DataCache<>(); - this.spawned = false; this.loggedIn = false; @@ -384,6 +387,8 @@ public class GeyserSession implements CommandSender { } downstream = new Client(remoteServer.getAddress(), remoteServer.getPort(), protocol, new TcpSessionFactory()); + // Let Geyser handle sending the keep alive + downstream.getSession().setFlag(MinecraftConstants.AUTOMATIC_KEEP_ALIVE_MANAGEMENT, false); downstream.getSession().addListener(new SessionAdapter() { @Override public void packetSending(PacketSendingEvent event) { 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 c4386481..6517f498 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 @@ -25,7 +25,6 @@ package org.geysermc.connector.network.translators; -import com.github.steveice10.mc.protocol.packet.ingame.server.ServerKeepAlivePacket; import com.github.steveice10.mc.protocol.packet.ingame.server.ServerPlayerListDataPacket; import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerUpdateLightPacket; import com.github.steveice10.packetlib.packet.Packet; @@ -75,7 +74,6 @@ public class PacketTranslatorRegistry { } } - IGNORED_PACKETS.add(ServerKeepAlivePacket.class); // Handled by MCProtocolLib IGNORED_PACKETS.add(ServerUpdateLightPacket.class); // Light is handled on Bedrock for us IGNORED_PACKETS.add(ServerPlayerListDataPacket.class); // Cant be implemented in bedrock } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/DataCache.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockNetworkStackLatencyTranslator.java similarity index 54% rename from connector/src/main/java/org/geysermc/connector/network/session/cache/DataCache.java rename to connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockNetworkStackLatencyTranslator.java index 4b2af963..d480b526 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/DataCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockNetworkStackLatencyTranslator.java @@ -23,15 +23,24 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.session.cache; +package org.geysermc.connector.network.translators.bedrock; -import lombok.Getter; +import com.github.steveice10.mc.protocol.packet.ingame.client.ClientKeepAlivePacket; +import com.nukkitx.protocol.bedrock.packet.NetworkStackLatencyPacket; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; -import java.util.HashMap; -import java.util.Map; +/** + * Used to send the keep alive packet back to the server + */ +@Translator(packet = NetworkStackLatencyPacket.class) +public class BedrockNetworkStackLatencyTranslator extends PacketTranslator { -public class DataCache { - - @Getter - private Map cachedValues = new HashMap(); + @Override + public void translate(NetworkStackLatencyPacket packet, GeyserSession session) { + // 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); + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaKeepAliveTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaKeepAliveTranslator.java new file mode 100644 index 00000000..1dd156e4 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaKeepAliveTranslator.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.java; + +import com.github.steveice10.mc.protocol.packet.ingame.server.ServerKeepAlivePacket; +import com.nukkitx.protocol.bedrock.packet.NetworkStackLatencyPacket; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; + +/** + * Used to forward the keep alive packet to the client in order to get back a reliable ping. + */ +@Translator(packet = ServerKeepAlivePacket.class) +public class JavaKeepAliveTranslator extends PacketTranslator { + + @Override + public void translate(ServerKeepAlivePacket packet, GeyserSession session) { + session.setLastKeepAliveTimestamp(packet.getPingId()); + NetworkStackLatencyPacket latencyPacket = new NetworkStackLatencyPacket(); + latencyPacket.setFromServer(true); + latencyPacket.setTimestamp(packet.getPingId()); + session.sendUpstreamPacket(latencyPacket); + } +} From ba6f174058dcfbc0e37aaa9af7e731bb18e17842 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Fri, 2 Oct 2020 15:44:46 -0400 Subject: [PATCH 055/205] Add support for fishing rods pulling Bedrock players (#1355) Fishing rods pulling players is a clientside feature on Java. On Bedrock, a SetEntityMotionPacket is sent to the client. Therefore this PR implements the Java fishing rod pulling mechanics and sends it off to Bedrock, which sends MovePlayerPackets that are sent to the server. --- .../entity/JavaEntityStatusTranslator.java | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) 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 ac2a80f7..c3fbd2e9 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 @@ -30,8 +30,8 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket; +import com.nukkitx.protocol.bedrock.packet.SetEntityMotionPacket; import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.entity.PlayerEntity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; @@ -96,8 +96,20 @@ public class JavaEntityStatusTranslator extends PacketTranslator Date: Wed, 7 Oct 2020 18:50:39 -0400 Subject: [PATCH 056/205] Jenkins improvements (#1368) - Show changes in the Discord webhook - Delete after 20 builds Co-authored-by: rtm516 --- Jenkinsfile | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index ffa4f1bd..1a93391d 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -5,7 +5,7 @@ pipeline { jdk 'Java 8' } options { - buildDiscarder(logRotator(artifactNumToKeepStr: '5')) + buildDiscarder(logRotator(artifactNumToKeepStr: '20')) } stages { stage ('Build') { @@ -32,9 +32,40 @@ pipeline { post { always { + script { + def changeLogSets = currentBuild.changeSets + def message = "**Changes:**" + + if (changeLogSets.size() == 0) { + message += "\n*No changes.*" + } else { + def repositoryUrl = scm.userRemoteConfigs[0].url.replace(".git", "") + def count = 0; + def extra = 0; + for (int i = 0; i < changeLogSets.size(); i++) { + def entries = changeLogSets[i].items + for (int j = 0; j < entries.length; j++) { + if (count <= 10) { + def entry = entries[j] + def commitId = entry.commitId.substring(0, 6) + message += "\n - [`${commitId}`](${repositoryUrl}/commit/${entry.commitId}) ${entry.msg}" + count++ + } else { + extra++; + } + } + } + + if (extra != 0) { + message += "\n - ${extra} more commits" + } + } + + env.changes = message + } deleteDir() withCredentials([string(credentialsId: 'geyser-discord-webhook', variable: 'DISCORD_WEBHOOK')]) { - discordSend description: "**Build:** [${currentBuild.id}](${env.BUILD_URL})\n**Status:** [${currentBuild.currentResult}](${env.BUILD_URL})\n\n[**Artifacts on Jenkins**](https://ci.nukkitx.com/job/Geyser)", footer: 'NukkitX Jenkins', link: env.BUILD_URL, successful: currentBuild.resultIsBetterOrEqualTo('SUCCESS'), title: "${env.JOB_NAME} #${currentBuild.id}", webhookURL: DISCORD_WEBHOOK + discordSend description: "**Build:** [${currentBuild.id}](${env.BUILD_URL})\n**Status:** [${currentBuild.currentResult}](${env.BUILD_URL})\n${changes}\n\n[**Artifacts on Jenkins**](https://ci.nukkitx.com/job/Geyser)", footer: 'Cloudburst Jenkins', link: env.BUILD_URL, successful: currentBuild.resultIsBetterOrEqualTo('SUCCESS'), title: "${env.JOB_NAME} #${currentBuild.id}", webhookURL: DISCORD_WEBHOOK } } } From 172a5a6db863624f65913d5672b9de60db1b70da Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Wed, 7 Oct 2020 18:51:36 -0400 Subject: [PATCH 057/205] Add Fabric as a platform type (#1376) * PlatformType: Add Fabric as a platform * Don't use XML reflections on Fabric --- .../org/geysermc/connector/GeyserConnector.java | 13 +++++++------ .../org/geysermc/connector/common/PlatformType.java | 3 ++- .../translators/PacketTranslatorRegistry.java | 2 +- .../network/translators/item/ItemTranslator.java | 2 +- .../translators/sound/SoundHandlerRegistry.java | 2 +- .../translators/world/block/BlockTranslator.java | 2 +- .../world/block/entity/BlockEntityTranslator.java | 2 +- 7 files changed, 14 insertions(+), 12 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 3c814393..92411897 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -312,15 +312,16 @@ public class GeyserConnector { } /** - * Get the production status of the current runtime. - * Will return true if the version number is not 'DEV'. - * Should only happen in compiled jars. + * 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. + * On Fabric - it complains about being unable to create a default XMLReader. + * On other platforms this should only be true in compiled jars. * - * @return If we are in a production build/environment + * @return whether to use XML reflections */ - public boolean isProduction() { + public boolean useXmlReflections() { //noinspection ConstantConditions - return !"DEV".equals(GeyserConnector.VERSION); + return !this.getPlatformType().equals(PlatformType.FABRIC) && !"DEV".equals(GeyserConnector.VERSION); } public static GeyserConnector getInstance() { diff --git a/connector/src/main/java/org/geysermc/connector/common/PlatformType.java b/connector/src/main/java/org/geysermc/connector/common/PlatformType.java index 4daa5d37..3e945d3a 100644 --- a/connector/src/main/java/org/geysermc/connector/common/PlatformType.java +++ b/connector/src/main/java/org/geysermc/connector/common/PlatformType.java @@ -34,10 +34,11 @@ public enum PlatformType { ANDROID("Android"), BUNGEECORD("BungeeCord"), + FABRIC("Fabric"), SPIGOT("Spigot"), SPONGE("Sponge"), STANDALONE("Standalone"), VELOCITY("Velocity"); - private String platformName; + private final String platformName; } 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 6517f498..b6c6f3ae 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 @@ -48,7 +48,7 @@ public class PacketTranslatorRegistry { private static final ObjectArrayList> IGNORED_PACKETS = new ObjectArrayList<>(); static { - Reflections ref = GeyserConnector.getInstance().isProduction() ? FileUtils.getReflections("org.geysermc.connector.network.translators") : new Reflections("org.geysermc.connector.network.translators"); + Reflections ref = GeyserConnector.getInstance().useXmlReflections() ? FileUtils.getReflections("org.geysermc.connector.network.translators") : new Reflections("org.geysermc.connector.network.translators"); for (Class clazz : ref.getTypesAnnotatedWith(Translator.class)) { Class packet = clazz.getAnnotation(Translator.class).packet(); 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 864e9fe0..ef629494 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 @@ -64,7 +64,7 @@ public abstract class ItemTranslator { static { /* Load item translators */ - Reflections ref = GeyserConnector.getInstance().isProduction() ? FileUtils.getReflections("org.geysermc.connector.network.translators.item") : new Reflections("org.geysermc.connector.network.translators.item"); + Reflections ref = GeyserConnector.getInstance().useXmlReflections() ? FileUtils.getReflections("org.geysermc.connector.network.translators.item") : new Reflections("org.geysermc.connector.network.translators.item"); Map loadedNbtItemTranslators = new HashMap<>(); for (Class clazz : ref.getTypesAnnotatedWith(ItemRemapper.class)) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/sound/SoundHandlerRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/sound/SoundHandlerRegistry.java index 163c451c..03b346e3 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/sound/SoundHandlerRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/sound/SoundHandlerRegistry.java @@ -40,7 +40,7 @@ public class SoundHandlerRegistry { static final Map> INTERACTION_HANDLERS = new HashMap<>(); static { - Reflections ref = GeyserConnector.getInstance().isProduction() ? FileUtils.getReflections("org.geysermc.connector.network.translators.sound") : new Reflections("org.geysermc.connector.network.translators.sound"); + Reflections ref = GeyserConnector.getInstance().useXmlReflections() ? FileUtils.getReflections("org.geysermc.connector.network.translators.sound") : new Reflections("org.geysermc.connector.network.translators.sound"); for (Class clazz : ref.getTypesAnnotatedWith(SoundHandler.class)) { try { SoundInteractionHandler interactionHandler = (SoundInteractionHandler) clazz.newInstance(); 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 fc7c852c..e5f8d6aa 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 @@ -113,7 +113,7 @@ public class BlockTranslator { addedStatesMap.defaultReturnValue(-1); List paletteList = new ArrayList<>(); - Reflections ref = GeyserConnector.getInstance().isProduction() ? FileUtils.getReflections("org.geysermc.connector.network.translators.world.block.entity") : new Reflections("org.geysermc.connector.network.translators.world.block.entity"); + Reflections ref = GeyserConnector.getInstance().useXmlReflections() ? FileUtils.getReflections("org.geysermc.connector.network.translators.world.block.entity") : new Reflections("org.geysermc.connector.network.translators.world.block.entity"); ref.getTypesAnnotatedWith(BlockEntity.class); int waterRuntimeId = -1; 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 54f593a6..4df4fd95 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 @@ -68,7 +68,7 @@ public abstract class BlockEntityTranslator { } static { - Reflections ref = GeyserConnector.getInstance().isProduction() ? FileUtils.getReflections("org.geysermc.connector.network.translators.world.block.entity") : new Reflections("org.geysermc.connector.network.translators.world.block.entity"); + Reflections ref = GeyserConnector.getInstance().useXmlReflections() ? FileUtils.getReflections("org.geysermc.connector.network.translators.world.block.entity") : new Reflections("org.geysermc.connector.network.translators.world.block.entity"); for (Class clazz : ref.getTypesAnnotatedWith(BlockEntity.class)) { GeyserConnector.getInstance().getLogger().debug("Found annotated block entity: " + clazz.getCanonicalName()); From 16eb2a491a0e7d9b12061fb7dd91b516bb15ae20 Mon Sep 17 00:00:00 2001 From: Arktisfox <65837019+Arktisfox@users.noreply.github.com> Date: Thu, 8 Oct 2020 18:33:36 -0400 Subject: [PATCH 058/205] Area cloud fixes (#684) * Fix NotNull error with particles, replace incorrect string meta with int meta. * Add back newline * Remove debug line * Update Protocol and prepare for merge Co-authored-by: DoctorMacc --- connector/pom.xml | 2 +- .../entity/AreaEffectCloudEntity.java | 7 +- .../network/UpstreamPacketHandler.java | 2 +- .../translators/effect/EffectRegistry.java | 72 +++++++++++++------ connector/src/main/resources/languages | 2 +- connector/src/main/resources/mappings | 2 +- 6 files changed, 59 insertions(+), 28 deletions(-) diff --git a/connector/pom.xml b/connector/pom.xml index 26ec0c4e..741acee5 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -33,7 +33,7 @@ com.github.CloudburstMC.Protocol bedrock-v408 - 250beb2a94 + 02f46a8700 compile 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 21861589..567a08ed 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/AreaEffectCloudEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/AreaEffectCloudEntity.java @@ -50,11 +50,12 @@ public class AreaEffectCloudEntity extends Entity { if (entityMetadata.getId() == 7) { metadata.put(EntityData.AREA_EFFECT_CLOUD_RADIUS, entityMetadata.getValue()); metadata.put(EntityData.BOUNDING_BOX_WIDTH, 2.0f * (float) entityMetadata.getValue()); - } else if (entityMetadata.getId() == 10) { - Particle particle = (Particle) entityMetadata.getValue(); - metadata.put(EntityData.AREA_EFFECT_CLOUD_PARTICLE_ID, EffectRegistry.getParticleString(particle.getType())); } else if (entityMetadata.getId() == 8) { metadata.put(EntityData.POTION_AUX_VALUE, entityMetadata.getValue()); + } else if (entityMetadata.getId() == 10) { + Particle particle = (Particle) entityMetadata.getValue(); + int particleId = EffectRegistry.getParticleId(particle.getType()); + metadata.put(EntityData.AREA_EFFECT_CLOUD_PARTICLE_ID, particleId); } super.updateBedrockMetadata(entityMetadata, session); } 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 7e97d429..f76d64ed 100644 --- a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java @@ -117,7 +117,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { case HAVE_ALL_PACKS: ResourcePackStackPacket stackPacket = new ResourcePackStackPacket(); - stackPacket.setExperimental(false); + stackPacket.setExperimentsPreviouslyToggled(false); stackPacket.setForcedToAccept(false); // Leaving this as false allows the player to choose to download or not stackPacket.setGameVersion(session.getClientData().getGameVersion()); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/effect/EffectRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/effect/EffectRegistry.java index 39c586db..75cab152 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/effect/EffectRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/effect/EffectRegistry.java @@ -32,10 +32,11 @@ import com.nukkitx.protocol.bedrock.data.LevelEventType; import com.nukkitx.protocol.bedrock.data.SoundEvent; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import lombok.NonNull; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.utils.FileUtils; -import org.geysermc.connector.utils.LanguageUtils; import java.io.InputStream; import java.util.HashMap; @@ -50,8 +51,19 @@ public class EffectRegistry { public static final Map SOUND_EFFECTS = new HashMap<>(); public static final Int2ObjectMap RECORDS = new Int2ObjectOpenHashMap<>(); - private static Map particleTypeMap = new HashMap<>(); - private static Map particleStringMap = new HashMap<>(); + /** + * Java particle type to Bedrock particle ID + * Used for area effect clouds. + */ + private static final Object2IntMap PARTICLE_TO_ID = new Object2IntOpenHashMap<>(); + /** + * Java particle type to Bedrock level event + */ + private static final Map PARTICLE_TO_LEVEL_EVENT = new HashMap<>(); + /** + * Java particle type to Bedrock namespaced string ID + */ + private static final Map PARTICLE_TO_STRING = new HashMap<>(); public static void init() { // no-op @@ -68,22 +80,24 @@ public class EffectRegistry { } Iterator> particlesIterator = particleEntries.fields(); - while (particlesIterator.hasNext()) { - Map.Entry entry = particlesIterator.next(); - try { - particleTypeMap.put(ParticleType.valueOf(entry.getKey().toUpperCase()), LevelEventType.valueOf(entry.getValue().asText().toUpperCase())); - } catch (IllegalArgumentException e1) { - try { - particleStringMap.put(ParticleType.valueOf(entry.getKey().toUpperCase()), entry.getValue().asText()); - GeyserConnector.getInstance().getLogger().debug("Force to map particle " - + entry.getKey() - + "=>" - + entry.getValue().asText() - + ", it will take effect."); - } catch (IllegalArgumentException e2){ - GeyserConnector.getInstance().getLogger().warning(LanguageUtils.getLocaleStringLog("geyser.particle.failed_map", entry.getKey(), entry.getValue().asText())); + try { + while (particlesIterator.hasNext()) { + Map.Entry entry = particlesIterator.next(); + JsonNode bedrockId = entry.getValue().get("bedrockId"); + JsonNode bedrockIdNumeric = entry.getValue().get("bedrockNumericId"); + JsonNode eventType = entry.getValue().get("eventType"); + if (bedrockIdNumeric != null) { + PARTICLE_TO_ID.put(ParticleType.valueOf(entry.getKey().toUpperCase()), bedrockIdNumeric.asInt()); + } + if (bedrockId != null) { + PARTICLE_TO_STRING.put(ParticleType.valueOf(entry.getKey().toUpperCase()), bedrockId.asText()); + } + if (eventType != null) { + PARTICLE_TO_LEVEL_EVENT.put(ParticleType.valueOf(entry.getKey().toUpperCase()), LevelEventType.valueOf(eventType.asText().toUpperCase())); } } + } catch (Exception e) { + e.printStackTrace(); } /* Load effects */ @@ -149,11 +163,27 @@ public class EffectRegistry { } } - public static LevelEventType getParticleLevelEventType(@NonNull ParticleType type) { - return particleTypeMap.getOrDefault(type, null); + /** + * @param type the Java particle to search for + * @return the Bedrock integer ID of the particle, or -1 if it does not exist + */ + public static int getParticleId(@NonNull ParticleType type) { + return PARTICLE_TO_ID.getOrDefault(type, -1); } - public static String getParticleString(@NonNull ParticleType type){ - return particleStringMap.getOrDefault(type, null); + /** + * @param type the Java particle to search for + * @return the level event equivalent Bedrock particle + */ + public static LevelEventType getParticleLevelEventType(@NonNull ParticleType type) { + return PARTICLE_TO_LEVEL_EVENT.getOrDefault(type, null); + } + + /** + * @param type the Java particle to search for + * @return the namespaced ID equivalent for Bedrock + */ + public static String getParticleString(@NonNull ParticleType type) { + return PARTICLE_TO_STRING.getOrDefault(type, null); } } diff --git a/connector/src/main/resources/languages b/connector/src/main/resources/languages index 93b2caed..5f217922 160000 --- a/connector/src/main/resources/languages +++ b/connector/src/main/resources/languages @@ -1 +1 @@ -Subproject commit 93b2caed3c4ecd94b3c77a87f1b2304a7bf4f062 +Subproject commit 5f21792264a364e32425014e0be79db93593da1e diff --git a/connector/src/main/resources/mappings b/connector/src/main/resources/mappings index 28a22d2b..0fae8d3f 160000 --- a/connector/src/main/resources/mappings +++ b/connector/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit 28a22d2baad680f511bffc36d90d06bf626f0527 +Subproject commit 0fae8d3f0de6210a10435a36128db14cb7650ae6 From 4514167835e44838980c7202204a769cd1784653 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Thu, 8 Oct 2020 18:44:15 -0400 Subject: [PATCH 059/205] Fix fire being punched in all directions (#1370) Apply the fix we have for fire but for all block faces. --- ...BedrockInventoryTransactionTranslator.java | 24 ++---------------- .../player/BedrockActionTranslator.java | 19 +++++++------- .../geysermc/connector/utils/BlockUtils.java | 25 +++++++++++++++++++ 3 files changed, 37 insertions(+), 31 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java index 8e891d92..b92a84eb 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java @@ -58,6 +58,7 @@ import org.geysermc.connector.network.translators.item.ItemEntry; import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.network.translators.sound.EntitySoundInteractionHandler; import org.geysermc.connector.network.translators.world.block.BlockTranslator; +import org.geysermc.connector.utils.BlockUtils; import org.geysermc.connector.utils.InventoryUtils; @Translator(packet = InventoryTransactionPacket.class) @@ -151,28 +152,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator Date: Fri, 9 Oct 2020 01:30:05 +0200 Subject: [PATCH 060/205] Various Scoreboard fixes (#1381) * Various Scoreboard fixes Fixes #1328 and a few other potential Scoreboard problems * Consistent whitespacing Co-authored-by: DoctorMacc --- .../connector/scoreboard/Objective.java | 12 ++----- .../geysermc/connector/scoreboard/Score.java | 1 - .../connector/scoreboard/Scoreboard.java | 31 ++++++++----------- 3 files changed, 15 insertions(+), 29 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/Objective.java b/connector/src/main/java/org/geysermc/connector/scoreboard/Objective.java index b2f64861..3accbc12 100644 --- a/connector/src/main/java/org/geysermc/connector/scoreboard/Objective.java +++ b/connector/src/main/java/org/geysermc/connector/scoreboard/Objective.java @@ -76,7 +76,8 @@ public class Objective { if (!scores.containsKey(id)) { Score score1 = new Score(this, id) .setScore(score) - .setTeam(scoreboard.getTeamFor(id)); + .setTeam(scoreboard.getTeamFor(id)) + .setUpdateType(UpdateType.ADD); scores.put(id, score1); } } @@ -96,15 +97,6 @@ public class Objective { return 0; } - public Score getScore(int line) { - for (Score score : scores.values()) { - if (score.getScore() == line) { - return score; - } - } - return null; - } - public void removeScore(String id) { if (scores.containsKey(id)) { scores.get(id).setUpdateType(UpdateType.REMOVE); diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/Score.java b/connector/src/main/java/org/geysermc/connector/scoreboard/Score.java index 635bafa3..3dfd6ed3 100644 --- a/connector/src/main/java/org/geysermc/connector/scoreboard/Score.java +++ b/connector/src/main/java/org/geysermc/connector/scoreboard/Score.java @@ -49,7 +49,6 @@ public class Score { this.id = objective.getScoreboard().getNextId().getAndIncrement(); this.objective = objective; this.name = name; - update(); } public String getDisplayName() { diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java b/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java index ae1b8275..732a056e 100644 --- a/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java +++ b/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java @@ -122,7 +122,7 @@ public class Scoreboard { for (Objective objective : objectives.values()) { if (!objective.isActive()) { - logger.debug("Ignoring non-active Scoreboard Objective '"+ objective.getObjectiveName() +'\''); + logger.debug("Ignoring non-active Scoreboard Objective '" + objective.getObjectiveName() + '\''); continue; } @@ -152,10 +152,6 @@ public class Scoreboard { boolean globalAdd = objective.getUpdateType() == ADD; boolean globalRemove = objective.getUpdateType() == REMOVE; - // Track if any scores changed - // Used to delete and resend scoreboard objectives; otherwise they won't update on Bedrock - boolean scoreChanged = false; - for (Score score : objective.getScores().values()) { Team team = score.getTeam(); @@ -171,16 +167,21 @@ public class Scoreboard { teamChanged |= team.getUpdateType() == UPDATE; add |= team.getUpdateType() == ADD || team.getUpdateType() == UPDATE; - remove |= team.getUpdateType() == REMOVE; + remove |= team.getUpdateType() != NOTHING; } add |= score.getUpdateType() == ADD || score.getUpdateType() == UPDATE; - remove |= score.getUpdateType() == REMOVE; - if (score.getUpdateType() == REMOVE) { + remove |= score.getUpdateType() == REMOVE || score.getUpdateType() == UPDATE; + + if (score.getUpdateType() == REMOVE || globalRemove) { add = false; } - if (score.getUpdateType() == UPDATE || teamChanged) { + if (score.getUpdateType() == ADD) { + remove = false; + } + + if (score.getUpdateType() == ADD || score.getUpdateType() == UPDATE || teamChanged) { score.update(); } @@ -191,12 +192,6 @@ public class Scoreboard { removeScores.add(score.getCachedInfo()); } - if (add || remove) { - scoreChanged = true; - } - // score is pending to be updated, so we use the current score as the old score - score.setOldScore(score.getScore()); - // score is pending to be removed, so we can remove it from the objective if (score.getUpdateType() == REMOVE) { objective.removeScore0(score.getName()); @@ -205,17 +200,17 @@ public class Scoreboard { score.setUpdateType(NOTHING); } - if (globalRemove || globalUpdate || scoreChanged) { + if (globalRemove || globalUpdate) { RemoveObjectivePacket removeObjectivePacket = new RemoveObjectivePacket(); removeObjectivePacket.setObjectiveId(objective.getObjectiveName()); session.sendUpstreamPacket(removeObjectivePacket); - if (objective.getUpdateType() == REMOVE) { + if (globalRemove) { objectives.remove(objective.getObjectiveName()); // now we can deregister objective.removed(); } } - if (globalAdd || globalUpdate || scoreChanged) { + if ((globalAdd || globalUpdate) && !globalRemove) { SetDisplayObjectivePacket displayObjectivePacket = new SetDisplayObjectivePacket(); displayObjectivePacket.setObjectiveId(objective.getObjectiveName()); displayObjectivePacket.setDisplayName(objective.getDisplayName()); From 59f72d0e65f3c421b92179b68d83c1b2ef1d7a4d Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Thu, 8 Oct 2020 20:07:50 -0400 Subject: [PATCH 061/205] BedrockMobEquipmentTranslator: Don't change item slot if already on that slot (#1353) * BedrockMobEquipmentTranslator: Don't change item slot if already on that slot * Update comment --- .../translators/bedrock/BedrockMobEquipmentTranslator.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMobEquipmentTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMobEquipmentTranslator.java index 02835151..a220e389 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMobEquipmentTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMobEquipmentTranslator.java @@ -40,7 +40,8 @@ public class BedrockMobEquipmentTranslator extends PacketTranslator 8 || - packet.getContainerId() != ContainerId.INVENTORY) { + packet.getContainerId() != ContainerId.INVENTORY || session.getInventory().getHeldItemSlot() == packet.getHotbarSlot()) { + // For the last condition - Don't update the slot if the slot is the same - not Java Edition behavior and messes with plugins such as Grief Prevention return; } From ec609fa86817666348af0cc96ceb452212250c32 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Thu, 8 Oct 2020 20:40:50 -0400 Subject: [PATCH 062/205] Make crossbows prettier (#1359) - Fix crossbow NBT translation - now crossbows will show as loaded - Pillagers now more closely resemble Java Edition pose behavior --- .../living/monster/raid/PillagerEntity.java | 49 +++++++++++++++++++ .../connector/entity/type/EntityType.java | 3 +- .../translators/nbt/CrossbowTranslator.java | 26 ++++++---- 3 files changed, 67 insertions(+), 11 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/entity/living/monster/raid/PillagerEntity.java diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/raid/PillagerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/raid/PillagerEntity.java new file mode 100644 index 00000000..c7a8f24d --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/raid/PillagerEntity.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.entity.living.monster.raid; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class PillagerEntity extends AbstractIllagerEntity { + + public PillagerEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 16) { + // Java Edition always has the Pillager entity as positioning the crossbow + metadata.getFlags().setFlag(EntityFlag.USING_ITEM, true); + metadata.getFlags().setFlag(EntityFlag.CHARGED, true); + } + super.updateBedrockMetadata(entityMetadata, session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java index 500e135e..24c15018 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 @@ -34,6 +34,7 @@ import org.geysermc.connector.entity.living.animal.tameable.*; import org.geysermc.connector.entity.living.merchant.*; import org.geysermc.connector.entity.living.monster.*; import org.geysermc.connector.entity.living.monster.raid.AbstractIllagerEntity; +import org.geysermc.connector.entity.living.monster.raid.PillagerEntity; import org.geysermc.connector.entity.living.monster.raid.RaidParticipantEntity; import org.geysermc.connector.entity.living.monster.raid.SpellcasterIllagerEntity; @@ -90,7 +91,7 @@ public enum EntityType { ENDERMITE(MonsterEntity.class, 55, 0.3f, 0.4f), AGENT(Entity.class, 56, 0f), VINDICATOR(AbstractIllagerEntity.class, 57, 1.8f, 0.6f, 0.6f, 1.62f), - PILLAGER(AbstractIllagerEntity.class, 114, 1.8f, 0.6f, 0.6f, 1.62f), + PILLAGER(PillagerEntity.class, 114, 1.8f, 0.6f, 0.6f, 1.62f), WANDERING_TRADER(AbstractMerchantEntity.class, 118, 1.8f, 0.6f, 0.6f, 1.62f), PHANTOM(FlyingEntity.class, 58, 0.5f, 0.9f, 0.9f, 0.6f), RAVAGER(RaidParticipantEntity.class, 59, 1.9f, 1.2f), diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/CrossbowTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/CrossbowTranslator.java index 979c5a20..67f137ff 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/CrossbowTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/CrossbowTranslator.java @@ -25,14 +25,15 @@ package org.geysermc.connector.network.translators.item.translators.nbt; -import com.github.steveice10.opennbt.tag.builtin.ByteTag; -import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import com.github.steveice10.opennbt.tag.builtin.ListTag; -import com.github.steveice10.opennbt.tag.builtin.StringTag; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.github.steveice10.opennbt.tag.builtin.*; +import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.ItemRemapper; -import org.geysermc.connector.network.translators.item.NbtItemStackTranslator; import org.geysermc.connector.network.translators.item.ItemEntry; +import org.geysermc.connector.network.translators.item.ItemRegistry; +import org.geysermc.connector.network.translators.item.ItemTranslator; +import org.geysermc.connector.network.translators.item.NbtItemStackTranslator; @ItemRemapper public class CrossbowTranslator extends NbtItemStackTranslator { @@ -44,12 +45,17 @@ public class CrossbowTranslator extends NbtItemStackTranslator { if (!chargedProjectiles.getValue().isEmpty()) { CompoundTag projectile = (CompoundTag) chargedProjectiles.getValue().get(0); - CompoundTag newProjectile = new CompoundTag("chargedItem"); - newProjectile.put(new ByteTag("Count", (byte) projectile.get("Count").getValue())); - newProjectile.put(new StringTag("Name", (String) projectile.get("id").getValue())); + ItemEntry entry = ItemRegistry.getItemEntry((String) projectile.get("id").getValue()); + if (entry == null) return; + CompoundTag tag = projectile.get("tag"); + ItemStack itemStack = new ItemStack(itemEntry.getJavaId(), (byte) projectile.get("Count").getValue(), tag); + ItemData itemData = ItemTranslator.translateToBedrock(session, itemStack); - // Not sure what this is for - newProjectile.put(new ByteTag("Damage", (byte) 0)); + CompoundTag newProjectile = new CompoundTag("chargedItem"); + newProjectile.put(new ByteTag("Count", (byte) itemData.getCount())); + newProjectile.put(new StringTag("Name", ItemRegistry.getBedrockIdentifer(entry))); + + newProjectile.put(new ShortTag("Damage", itemData.getDamage())); itemTag.put(newProjectile); } From ffcff96bea6e43ec34725c648eb5e9c76cad40ea Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Sat, 10 Oct 2020 18:08:21 -0400 Subject: [PATCH 063/205] Set default values for classes as well (#1387) Geyser can now start even if the config file is empty. Tested on Spigot and doesn't affect custom values. --- .../configuration/GeyserJacksonConfiguration.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) 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 99a3a7a5..45676fbd 100644 --- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java +++ b/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java @@ -34,6 +34,7 @@ import org.geysermc.connector.common.serializer.AsteriskSerializer; import java.nio.file.Path; import java.util.Map; +import java.util.UUID; @Getter @JsonIgnoreProperties(ignoreUnknown = true) @@ -45,8 +46,8 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration @Setter private boolean autoconfiguredRemote = false; - private BedrockConfiguration bedrock; - private RemoteConfiguration remote; + private BedrockConfiguration bedrock = new BedrockConfiguration(); + private RemoteConfiguration remote = new RemoteConfiguration(); @JsonProperty("floodgate-key-file") private String floodgateKeyFile = "public-key.pem"; @@ -106,7 +107,7 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration @JsonProperty("force-resource-packs") private boolean forceResourcePacks = true; - private MetricsInfo metrics; + private MetricsInfo metrics = new MetricsInfo(); @Getter public static class BedrockConfiguration implements IBedrockConfiguration { @@ -154,7 +155,7 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration private boolean enabled = true; @JsonProperty("uuid") - private String uniqueId = "generateuuid"; + private String uniqueId = UUID.randomUUID().toString(); } @JsonProperty("scoreboard-packet-threshold") From 96db37c14c924f9bce1c827d25416817281e2a6f Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Mon, 12 Oct 2020 20:02:41 -0400 Subject: [PATCH 064/205] Fix bucket interactions on creative mode (#1369) * Fix bucket interactions on creative mode Bedrock uses the BLOCK_INTERACT enum of BedrockActionTranslator to truly indicate if a bucket should be used or not. In order to hook into this, we need to delay the bucket placing by about 5 milliseconds - this gives us time to cancel the interaction if needed. Bucket sounds will now not play in this case as well. --- .../network/session/GeyserSession.java | 8 ++++++++ ...BedrockInventoryTransactionTranslator.java | 20 ++++++++++--------- .../player/BedrockActionTranslator.java | 7 ++++++- .../block/BucketSoundInteractionHandler.java | 2 ++ 4 files changed, 27 insertions(+), 10 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 5225632c..1a0bbfb2 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 @@ -84,6 +84,7 @@ import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.security.spec.InvalidKeySpecException; import java.util.*; +import java.util.concurrent.ScheduledFuture; import java.util.concurrent.atomic.AtomicInteger; @Getter @@ -210,6 +211,13 @@ public class GeyserSession implements CommandSender { @Setter private long lastInteractionTime; + /** + * Stores a future interaction to place a bucket. Will be cancelled if the client instead intended to + * interact with a block. + */ + @Setter + private ScheduledFuture bucketScheduledFuture; + private boolean reducedDebugInfo = false; @Setter diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java index b92a84eb..b81025be 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java @@ -61,6 +61,8 @@ import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.utils.BlockUtils; import org.geysermc.connector.utils.InventoryUtils; +import java.util.concurrent.TimeUnit; + @Translator(packet = InventoryTransactionPacket.class) public class BedrockInventoryTransactionTranslator extends PacketTranslator { @@ -120,18 +122,19 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator { + ClientPlayerUseItemPacket itemPacket = new ClientPlayerUseItemPacket(Hand.MAIN_HAND); + session.sendDownstreamPacket(itemPacket); + }, 5, TimeUnit.MILLISECONDS)); } if (packet.getActions().isEmpty()) { @@ -167,10 +170,9 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator Date: Tue, 13 Oct 2020 02:17:15 +0100 Subject: [PATCH 065/205] Add player device OS to metrics (#1391) * Add player device os to metrics * Add player version, Geyser version, and default locale Co-authored-by: DoctorMacc --- .../geysermc/connector/GeyserConnector.java | 35 ++++++++++++++++++- 1 file changed, 34 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 92411897..109a5a60 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -66,7 +66,9 @@ import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.text.DecimalFormat; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -199,8 +201,39 @@ public class GeyserConnector { metrics = new Metrics(this, "GeyserMC", config.getMetrics().getUniqueId(), false, java.util.logging.Logger.getLogger("")); metrics.addCustomChart(new Metrics.SingleLineChart("servers", () -> 1)); metrics.addCustomChart(new Metrics.SingleLineChart("players", players::size)); - metrics.addCustomChart(new Metrics.SimplePie("authMode", authType.name()::toLowerCase)); + // Prevent unwanted words best we can + metrics.addCustomChart(new Metrics.SimplePie("authMode", () -> AuthType.getByName(config.getRemote().getAuthType()).toString())); metrics.addCustomChart(new Metrics.SimplePie("platform", platformType::getPlatformName)); + metrics.addCustomChart(new Metrics.SimplePie("defaultLocale", LanguageUtils::getDefaultLocale)); + metrics.addCustomChart(new Metrics.SimplePie("version", () -> GeyserConnector.VERSION)); + metrics.addCustomChart(new Metrics.AdvancedPie("playerPlatform", () -> { + Map valueMap = new HashMap<>(); + for (GeyserSession session : players) { + if (session == null) continue; + if (session.getClientData() == null) continue; + String os = session.getClientData().getDeviceOS().toString(); + if (!valueMap.containsKey(os)) { + valueMap.put(os, 1); + } else { + valueMap.put(os, valueMap.get(os) + 1); + } + } + return valueMap; + })); + metrics.addCustomChart(new Metrics.AdvancedPie("playerVersion", () -> { + Map valueMap = new HashMap<>(); + for (GeyserSession session : players) { + if (session == null) continue; + if (session.getClientData() == null) continue; + String version = session.getClientData().getGameVersion(); + if (!valueMap.containsKey(version)) { + valueMap.put(version, 1); + } else { + valueMap.put(version, valueMap.get(version) + 1); + } + } + return valueMap; + })); } boolean isGui = false; From 1b00eaca4aa722ff15647879c04e9708dc704458 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Mon, 12 Oct 2020 21:28:54 -0400 Subject: [PATCH 066/205] Set AuthType in Metrics to lowercase (#1395) --- .../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 109a5a60..afed4dfd 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -202,7 +202,7 @@ public class GeyserConnector { metrics.addCustomChart(new Metrics.SingleLineChart("servers", () -> 1)); metrics.addCustomChart(new Metrics.SingleLineChart("players", players::size)); // Prevent unwanted words best we can - metrics.addCustomChart(new Metrics.SimplePie("authMode", () -> AuthType.getByName(config.getRemote().getAuthType()).toString())); + metrics.addCustomChart(new Metrics.SimplePie("authMode", () -> AuthType.getByName(config.getRemote().getAuthType()).toString().toLowerCase())); metrics.addCustomChart(new Metrics.SimplePie("platform", platformType::getPlatformName)); metrics.addCustomChart(new Metrics.SimplePie("defaultLocale", LanguageUtils::getDefaultLocale)); metrics.addCustomChart(new Metrics.SimplePie("version", () -> GeyserConnector.VERSION)); From 9b3cd8f725ff26e6a0abfd87481f021d959a9be6 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Mon, 12 Oct 2020 20:35:31 -0500 Subject: [PATCH 067/205] Fix area effect clouds --- .../geysermc/connector/entity/AreaEffectCloudEntity.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) 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 567a08ed..308d2121 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/AreaEffectCloudEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/AreaEffectCloudEntity.java @@ -43,6 +43,8 @@ public class AreaEffectCloudEntity extends Entity { // This disabled client side shrink of the cloud metadata.put(EntityData.AREA_EFFECT_CLOUD_RADIUS, 0.0f); + metadata.put(EntityData.AREA_EFFECT_CLOUD_CHANGE_RATE, -0.005f); + metadata.put(EntityData.AREA_EFFECT_CLOUD_CHANGE_ON_PICKUP, -0.5f); } @Override @@ -51,11 +53,13 @@ public class AreaEffectCloudEntity extends Entity { 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) { - metadata.put(EntityData.POTION_AUX_VALUE, entityMetadata.getValue()); + metadata.put(EntityData.EFFECT_COLOR, entityMetadata.getValue()); } else if (entityMetadata.getId() == 10) { Particle particle = (Particle) entityMetadata.getValue(); int particleId = EffectRegistry.getParticleId(particle.getType()); - metadata.put(EntityData.AREA_EFFECT_CLOUD_PARTICLE_ID, particleId); + if (particleId != -1) { + metadata.put(EntityData.AREA_EFFECT_CLOUD_PARTICLE_ID, particleId); + } } super.updateBedrockMetadata(entityMetadata, session); } From 191777773c5da59e4752e86dfeed202517af4a19 Mon Sep 17 00:00:00 2001 From: DaPorkchop_ Date: Tue, 13 Oct 2020 16:44:47 +0200 Subject: [PATCH 068/205] Don't use wrapper objects for positions in ChunkCache (#1398) * make ChunkPosition use a hashCode implementation with far better hash distribution this should improve the performance when used as a hash table key * ChunkCache no longer uses position wrapper objects this yields a roughly 15-20% increase in performance when converting chunk data * fix code style issues --- .../network/session/cache/ChunkCache.java | 87 ++++++++++--------- .../java/world/JavaUnloadChunkTranslator.java | 3 +- .../translators/world/GeyserWorldManager.java | 6 +- .../world/chunk/ChunkPosition.java | 30 ++++++- .../geysermc/connector/utils/ChunkUtils.java | 5 +- 5 files changed, 79 insertions(+), 52 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 2cc9ea13..14825b71 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 @@ -27,22 +27,18 @@ package org.geysermc.connector.network.session.cache; 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.metadata.Position; -import lombok.Getter; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import org.geysermc.connector.bootstrap.GeyserBootstrap; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.network.translators.world.chunk.ChunkPosition; -import java.util.HashMap; -import java.util.Map; - public class ChunkCache { private final boolean cache; - @Getter - private final Map chunks = new HashMap<>(); + private final Long2ObjectMap chunks = new Long2ObjectOpenHashMap<>(); public ChunkCache(GeyserSession session) { if (session.getConnector().getWorldManager().getClass() == GeyserBootstrap.DEFAULT_CHUNK_MANAGER.getClass()) { @@ -56,57 +52,66 @@ public class ChunkCache { if (!cache) { return; } - ChunkPosition position = new ChunkPosition(chunk.getX(), chunk.getZ()); - if (chunk.getBiomeData() == null && chunks.containsKey(position)) { - Column newColumn = chunk; - chunk = chunks.get(position); - for (int i = 0; i < newColumn.getChunks().length; i++) { - if (newColumn.getChunks()[i] != null) { - chunk.getChunks()[i] = newColumn.getChunks()[i]; + + long chunkPosition = ChunkPosition.toLong(chunk.getX(), chunk.getZ()); + Column existingChunk; + if (chunk.getBiomeData() != null // Only consider merging columns if the new chunk isn't a full chunk + && (existingChunk = chunks.getOrDefault(chunkPosition, null)) != null) { // Column is already present in cache, we can merge with existing + for (int i = 0; i < chunk.getChunks().length; i++) { // The chunks member is final, so chunk.getChunks() will probably be inlined and then completely optimized away + if (chunk.getChunks()[i] != null) { + existingChunk.getChunks()[i] = chunk.getChunks()[i]; } } - } - chunks.put(position, chunk); - } - - public void updateBlock(Position position, int block) { - if (!cache) { - return; - } - ChunkPosition chunkPosition = new ChunkPosition(position.getX() >> 4, position.getZ() >> 4); - if (!chunks.containsKey(chunkPosition)) - return; - - Column column = chunks.get(chunkPosition); - Chunk chunk = column.getChunks()[position.getY() >> 4]; - Position blockPosition = chunkPosition.getChunkBlock(position.getX(), position.getY(), position.getZ()); - if (chunk != null) { - chunk.set(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ(), block); + } else { + chunks.put(chunkPosition, chunk); } } - public int getBlockAt(Position position) { + public Column getChunk(int chunkX, int chunkZ) { + long chunkPosition = ChunkPosition.toLong(chunkX, chunkZ); + return chunks.getOrDefault(chunkPosition, null); + } + + public void updateBlock(int x, int y, int z, int block) { + if (!cache) { + return; + } + + Column column = this.getChunk(x >> 4, z >> 4); + if (column == null) { + return; + } + + Chunk chunk = column.getChunks()[y >> 4]; + if (chunk != null) { + chunk.set(x & 0xF, y & 0xF, z & 0xF, block); + } + } + + public int getBlockAt(int x, int y, int z) { if (!cache) { return BlockTranslator.AIR; } - ChunkPosition chunkPosition = new ChunkPosition(position.getX() >> 4, position.getZ() >> 4); - if (!chunks.containsKey(chunkPosition)) - return BlockTranslator.AIR; - Column column = chunks.get(chunkPosition); - Chunk chunk = column.getChunks()[position.getY() >> 4]; - Position blockPosition = chunkPosition.getChunkBlock(position.getX(), position.getY(), position.getZ()); + Column column = this.getChunk(x >> 4, z >> 4); + if (column == null) { + return BlockTranslator.AIR; + } + + Chunk chunk = column.getChunks()[y >> 4]; if (chunk != null) { - return chunk.get(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ()); + return chunk.get(x & 0xF, y & 0xF, z & 0xF); } return BlockTranslator.AIR; } - public void removeChunk(ChunkPosition position) { + public void removeChunk(int chunkX, int chunkZ) { if (!cache) { return; } - chunks.remove(position); + + long chunkPosition = ChunkPosition.toLong(chunkX, chunkZ); + chunks.remove(chunkPosition); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUnloadChunkTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUnloadChunkTranslator.java index d54d8b6a..652e1294 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUnloadChunkTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUnloadChunkTranslator.java @@ -28,7 +28,6 @@ package org.geysermc.connector.network.translators.java.world; 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.world.chunk.ChunkPosition; import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerUnloadChunkPacket; @@ -37,6 +36,6 @@ public class JavaUnloadChunkTranslator extends PacketTranslator Date: Tue, 13 Oct 2020 17:11:52 +0200 Subject: [PATCH 069/205] fix some NPEs caused by race conditions in chunk conversion (#1396) * fix some NPEs caused by race conditions in chunk conversion tbh the whole session should be read-write locked for every operation * fix code style issues --- .../java/world/JavaChunkDataTranslator.java | 6 +++--- .../translators/world/GeyserWorldManager.java | 21 +++++++++++++++---- 2 files changed, 20 insertions(+), 7 deletions(-) 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 6dae9b4d..ddd5e004 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 @@ -66,11 +66,11 @@ public class JavaChunkDataTranslator extends PacketTranslator { try { + // Non-full chunks don't have all the chunk data, and Bedrock won't accept that + final boolean isNonFullChunk = (packet.getColumn().getBiomeData() == null); + ChunkUtils.ChunkData chunkData = ChunkUtils.translateToBedrock(session, packet.getColumn(), isNonFullChunk); ByteBuf byteBuf = Unpooled.buffer(32); ChunkSection[] sections = chunkData.sections; 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 983e42b7..6972d77b 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,12 +25,14 @@ package org.geysermc.connector.network.translators.world; +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; import it.unimi.dsi.fastutil.objects.Object2ObjectMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.session.cache.ChunkCache; import org.geysermc.connector.utils.GameRule; public class GeyserWorldManager extends WorldManager { @@ -39,14 +41,25 @@ public class GeyserWorldManager extends WorldManager { @Override public int getBlockAt(GeyserSession session, int x, int y, int z) { - return session.getChunkCache().getBlockAt(x, y, z); + ChunkCache chunkCache = session.getChunkCache(); + if (chunkCache != null) { // Chunk cache can be null if the session is closed asynchronously + return chunkCache.getBlockAt(x, y, z); + } + return 0; } @Override public int[] getBiomeDataAt(GeyserSession session, int x, int z) { - if (!session.getConnector().getConfig().isCacheChunks()) - return new int[1024]; - return session.getChunkCache().getChunk(x, z).getBiomeData(); + 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 From 2ca2436cdc47a94dbf50e469eeeb33f99f6bb79c Mon Sep 17 00:00:00 2001 From: Tim203 Date: Thu, 15 Oct 2020 05:34:24 +0200 Subject: [PATCH 070/205] Don't use the general thread pool to run an async method (#1397) * Don't use the general thread pool for an async method * Align nested class at the bottom --- .../player/JavaPlayerListEntryTranslator.java | 5 +- .../geysermc/connector/utils/SkinUtils.java | 268 ++++++++---------- 2 files changed, 128 insertions(+), 145 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerListEntryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerListEntryTranslator.java index 6a9ef4dc..da402f66 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerListEntryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerListEntryTranslator.java @@ -57,9 +57,8 @@ public class JavaPlayerListEntryTranslator extends PacketTranslator { - GeyserConnector.getInstance().getLogger().debug("Loading Local Bedrock Java Skin Data"); - }); + SkinUtils.requestAndHandleSkinAndCape(playerEntity, session, skinAndCape -> + GeyserConnector.getInstance().getLogger().debug("Loaded Local Bedrock Java Skin Data")); } else { playerEntity = session.getEntityCache().getPlayerEntity(entry.getProfile().getId()); } diff --git a/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java b/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java index fe2a8aa9..5505acdf 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java @@ -33,9 +33,8 @@ import com.nukkitx.protocol.bedrock.data.skin.SerializedSkin; import com.nukkitx.protocol.bedrock.packet.PlayerListPacket; import lombok.AllArgsConstructor; import lombok.Getter; -import org.geysermc.connector.common.AuthType; import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.entity.Entity; +import org.geysermc.connector.common.AuthType; import org.geysermc.connector.entity.PlayerEntity; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.auth.BedrockClientData; @@ -73,21 +72,6 @@ public class SkinUtils { ); } - public static PlayerListPacket.Entry buildDefaultEntry(GeyserSession session, GameProfile profile, long geyserId) { - return buildEntryManually( - session, - profile.getId(), - profile.getName(), - geyserId, - "default", - SkinProvider.STEVE_SKIN, - SkinProvider.EMPTY_CAPE.getCapeId(), - SkinProvider.EMPTY_CAPE.getCapeData(), - SkinProvider.EMPTY_GEOMETRY.getGeometryName(), - SkinProvider.EMPTY_GEOMETRY.getGeometryData() - ); - } - public static PlayerListPacket.Entry buildEntryManually(GeyserSession session, UUID uuid, String username, long geyserId, String skinId, byte[] skinData, String capeId, byte[] capeData, @@ -115,7 +99,7 @@ public class SkinUtils { } else { entry = new PlayerListPacket.Entry(uuid); } - + entry.setName(username); entry.setEntityId(geyserId); entry.setSkin(serializedSkin); @@ -126,12 +110,133 @@ public class SkinUtils { return entry; } + public static void requestAndHandleSkinAndCape(PlayerEntity entity, GeyserSession session, + Consumer skinAndCapeConsumer) { + GameProfileData data = GameProfileData.from(entity.getProfile()); + + SkinProvider.requestSkinAndCape(entity.getUuid(), data.getSkinUrl(), data.getCapeUrl()) + .whenCompleteAsync((skinAndCape, throwable) -> { + try { + SkinProvider.Skin skin = skinAndCape.getSkin(); + SkinProvider.Cape cape = skinAndCape.getCape(); + + if (cape.isFailed()) { + cape = SkinProvider.getOrDefault(SkinProvider.requestBedrockCape( + entity.getUuid(), false + ), SkinProvider.EMPTY_CAPE, 3); + } + + if (cape.isFailed() && SkinProvider.ALLOW_THIRD_PARTY_CAPES) { + cape = SkinProvider.getOrDefault(SkinProvider.requestUnofficialCape( + cape, entity.getUuid(), + entity.getUsername(), false + ), SkinProvider.EMPTY_CAPE, SkinProvider.CapeProvider.VALUES.length * 3); + } + + SkinProvider.SkinGeometry geometry = SkinProvider.SkinGeometry.getLegacy(data.isAlex()); + geometry = SkinProvider.getOrDefault(SkinProvider.requestBedrockGeometry( + geometry, entity.getUuid(), false + ), geometry, 3); + + // Not a bedrock player check for ears + if (geometry.isFailed() && SkinProvider.ALLOW_THIRD_PARTY_EARS) { + boolean isEars; + + // Its deadmau5, gotta support his skin :) + if (entity.getUuid().toString().equals("1e18d5ff-643d-45c8-b509-43b8461d8614")) { + isEars = true; + } else { + // Get the ears texture for the player + skin = SkinProvider.getOrDefault(SkinProvider.requestUnofficialEars( + skin, entity.getUuid(), entity.getUsername(), false + ), skin, 3); + + isEars = skin.isEars(); + } + + // Does the skin have an ears texture + if (isEars) { + // Get the new geometry + geometry = SkinProvider.SkinGeometry.getEars(data.isAlex()); + + // Store the skin and geometry for the ears + SkinProvider.storeEarSkin(entity.getUuid(), skin); + SkinProvider.storeEarGeometry(entity.getUuid(), data.isAlex()); + } + } + + entity.setLastSkinUpdate(skin.getRequestedOn()); + + if (session.getUpstream().isInitialized()) { + PlayerListPacket.Entry updatedEntry = buildEntryManually( + session, + entity.getUuid(), + entity.getUsername(), + entity.getGeyserId(), + skin.getTextureUrl(), + skin.getSkinData(), + cape.getCapeId(), + cape.getCapeData(), + geometry.getGeometryName(), + geometry.getGeometryData() + ); + + + PlayerListPacket playerAddPacket = new PlayerListPacket(); + playerAddPacket.setAction(PlayerListPacket.Action.ADD); + playerAddPacket.getEntries().add(updatedEntry); + session.sendUpstreamPacket(playerAddPacket); + + if (!entity.isPlayerList()) { + PlayerListPacket playerRemovePacket = new PlayerListPacket(); + playerRemovePacket.setAction(PlayerListPacket.Action.REMOVE); + playerRemovePacket.getEntries().add(updatedEntry); + session.sendUpstreamPacket(playerRemovePacket); + + } + } + } catch (Exception e) { + GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.skin.fail", entity.getUuid()), e); + } + + if (skinAndCapeConsumer != null) { + skinAndCapeConsumer.accept(skinAndCape); + } + }); + } + + public static void handleBedrockSkin(PlayerEntity playerEntity, BedrockClientData clientData) { + GeyserConnector.getInstance().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.skin.bedrock.register", playerEntity.getUsername(), playerEntity.getUuid())); + + try { + byte[] skinBytes = Base64.getDecoder().decode(clientData.getSkinData().getBytes(StandardCharsets.UTF_8)); + byte[] capeBytes = clientData.getCapeData(); + + byte[] geometryNameBytes = Base64.getDecoder().decode(clientData.getGeometryName().getBytes(StandardCharsets.UTF_8)); + byte[] geometryBytes = Base64.getDecoder().decode(clientData.getGeometryData().getBytes(StandardCharsets.UTF_8)); + + if (skinBytes.length <= (128 * 128 * 4) && !clientData.isPersonaSkin()) { + SkinProvider.storeBedrockSkin(playerEntity.getUuid(), clientData.getSkinId(), skinBytes); + SkinProvider.storeBedrockGeometry(playerEntity.getUuid(), geometryNameBytes, geometryBytes); + } else { + GeyserConnector.getInstance().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.skin.bedrock.fail", playerEntity.getUsername())); + GeyserConnector.getInstance().getLogger().debug("The size of '" + playerEntity.getUsername() + "' skin is: " + clientData.getSkinImageWidth() + "x" + clientData.getSkinImageHeight()); + } + + if (!clientData.getCapeId().equals("")) { + SkinProvider.storeBedrockCape(playerEntity.getUuid(), capeBytes); + } + } catch (Exception e) { + throw new AssertionError("Failed to cache skin for bedrock user (" + playerEntity.getUsername() + "): ", e); + } + } + @AllArgsConstructor @Getter public static class GameProfileData { - private String skinUrl; - private String capeUrl; - private boolean alex; + private final String skinUrl; + private final String capeUrl; + private final boolean alex; /** * Generate the GameProfileData from the given GameProfile @@ -170,125 +275,4 @@ public class SkinUtils { } } } - - public static void requestAndHandleSkinAndCape(PlayerEntity entity, GeyserSession session, - Consumer skinAndCapeConsumer) { - GeyserConnector.getInstance().getGeneralThreadPool().execute(() -> { - GameProfileData data = GameProfileData.from(entity.getProfile()); - - SkinProvider.requestSkinAndCape(entity.getUuid(), data.getSkinUrl(), data.getCapeUrl()) - .whenCompleteAsync((skinAndCape, throwable) -> { - try { - SkinProvider.Skin skin = skinAndCape.getSkin(); - SkinProvider.Cape cape = skinAndCape.getCape(); - - if (cape.isFailed()) { - cape = SkinProvider.getOrDefault(SkinProvider.requestBedrockCape( - entity.getUuid(), false - ), SkinProvider.EMPTY_CAPE, 3); - } - - if (cape.isFailed() && SkinProvider.ALLOW_THIRD_PARTY_CAPES) { - cape = SkinProvider.getOrDefault(SkinProvider.requestUnofficialCape( - cape, entity.getUuid(), - entity.getUsername(), false - ), SkinProvider.EMPTY_CAPE, SkinProvider.CapeProvider.VALUES.length * 3); - } - - SkinProvider.SkinGeometry geometry = SkinProvider.SkinGeometry.getLegacy(data.isAlex()); - geometry = SkinProvider.getOrDefault(SkinProvider.requestBedrockGeometry( - geometry, entity.getUuid(), false - ), geometry, 3); - - // Not a bedrock player check for ears - if (geometry.isFailed() && SkinProvider.ALLOW_THIRD_PARTY_EARS) { - boolean isEars = false; - - // Its deadmau5, gotta support his skin :) - if (entity.getUuid().toString().equals("1e18d5ff-643d-45c8-b509-43b8461d8614")) { - isEars = true; - } else { - // Get the ears texture for the player - skin = SkinProvider.getOrDefault(SkinProvider.requestUnofficialEars( - skin, entity.getUuid(), entity.getUsername(), false - ), skin, 3); - - isEars = skin.isEars(); - } - - // Does the skin have an ears texture - if (isEars) { - // Get the new geometry - geometry = SkinProvider.SkinGeometry.getEars(data.isAlex()); - - // Store the skin and geometry for the ears - SkinProvider.storeEarSkin(entity.getUuid(), skin); - SkinProvider.storeEarGeometry(entity.getUuid(), data.isAlex()); - } - } - - entity.setLastSkinUpdate(skin.getRequestedOn()); - - if (session.getUpstream().isInitialized()) { - PlayerListPacket.Entry updatedEntry = buildEntryManually( - session, - entity.getUuid(), - entity.getUsername(), - entity.getGeyserId(), - skin.getTextureUrl(), - skin.getSkinData(), - cape.getCapeId(), - cape.getCapeData(), - geometry.getGeometryName(), - geometry.getGeometryData() - ); - - - PlayerListPacket playerAddPacket = new PlayerListPacket(); - playerAddPacket.setAction(PlayerListPacket.Action.ADD); - playerAddPacket.getEntries().add(updatedEntry); - session.sendUpstreamPacket(playerAddPacket); - - if (!entity.isPlayerList()) { - PlayerListPacket playerRemovePacket = new PlayerListPacket(); - playerRemovePacket.setAction(PlayerListPacket.Action.REMOVE); - playerRemovePacket.getEntries().add(updatedEntry); - session.sendUpstreamPacket(playerRemovePacket); - - } - } - } catch (Exception e) { - GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.skin.fail", entity.getUuid()), e); - } - - if (skinAndCapeConsumer != null) skinAndCapeConsumer.accept(skinAndCape); - }); - }); - } - - public static void handleBedrockSkin(PlayerEntity playerEntity, BedrockClientData clientData) { - GeyserConnector.getInstance().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.skin.bedrock.register", playerEntity.getUsername(), playerEntity.getUuid())); - - try { - byte[] skinBytes = Base64.getDecoder().decode(clientData.getSkinData().getBytes("UTF-8")); - byte[] capeBytes = clientData.getCapeData(); - - byte[] geometryNameBytes = Base64.getDecoder().decode(clientData.getGeometryName().getBytes("UTF-8")); - byte[] geometryBytes = Base64.getDecoder().decode(clientData.getGeometryData().getBytes("UTF-8")); - - if (skinBytes.length <= (128 * 128 * 4) && !clientData.isPersonaSkin()) { - SkinProvider.storeBedrockSkin(playerEntity.getUuid(), clientData.getSkinId(), skinBytes); - SkinProvider.storeBedrockGeometry(playerEntity.getUuid(), geometryNameBytes, geometryBytes); - } else { - GeyserConnector.getInstance().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.skin.bedrock.fail", playerEntity.getUsername())); - GeyserConnector.getInstance().getLogger().debug("The size of '" + playerEntity.getUsername() + "' skin is: " + clientData.getSkinImageWidth() + "x" + clientData.getSkinImageHeight()); - } - - if (!clientData.getCapeId().equals("")) { - SkinProvider.storeBedrockCape(playerEntity.getUuid(), capeBytes); - } - } catch (Exception e) { - throw new AssertionError("Failed to cache skin for bedrock user (" + playerEntity.getUsername() + "): ", e); - } - } } From 40de801eb061f3ea6dbef2c8acf56f321b46174f Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Thu, 15 Oct 2020 01:21:12 -0500 Subject: [PATCH 071/205] Add sound when an arrow hits a player --- .../java/world/JavaNotifyClientTranslator.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java index 493a7ca1..d7961dd9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java @@ -145,6 +145,14 @@ public class JavaNotifyClientTranslator extends PacketTranslator Date: Thu, 15 Oct 2020 08:30:25 +0200 Subject: [PATCH 072/205] Faster chunk conversion (#1400) * BlockStorage is never used concurrently, no need to synchronize * initial, semi-functional, faster chunk conversion * faster chunk conversion works well for every situation except spigot * delete unused ChunkPosition class * preallocate and pool chunk encoding buffers * make it work correctly on spigot * make field naming more consistent * attempt to upgrade to latest MCProtocolLib * remove debug code * compile against my MCProtocolLib fork while i wait for my upstream PR to be accepted * return to Steveice10 MCProtocolLib --- .../world/GeyserSpigotWorldManager.java | 63 +++++- connector/pom.xml | 2 +- .../network/session/cache/ChunkCache.java | 18 +- .../java/world/JavaChunkDataTranslator.java | 81 ++++--- .../translators/world/GeyserWorldManager.java | 26 +++ .../translators/world/WorldManager.java | 22 ++ .../translators/world/chunk/BlockStorage.java | 26 ++- .../world/chunk/ChunkPosition.java | 79 ------- .../translators/world/chunk/ChunkSection.java | 65 +----- .../world/chunk/bitarray/BitArrayVersion.java | 18 +- .../geysermc/connector/utils/ChunkUtils.java | 213 +++++++++++++----- .../geysermc/connector/utils/MathUtils.java | 11 + 12 files changed, 371 insertions(+), 253 deletions(-) delete mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/ChunkPosition.java diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java index c6443bd0..8a92526f 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java @@ -26,12 +26,14 @@ package org.geysermc.platform.spigot.world; import com.fasterxml.jackson.databind.JsonNode; +import com.github.steveice10.mc.protocol.data.game.chunk.Chunk; 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.entity.Player; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.GeyserWorldManager; @@ -93,23 +95,32 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { @Override public int getBlockAt(GeyserSession session, int x, int y, int z) { - if (session.getPlayerEntity() == null) { - return BlockTranslator.AIR; - } - if (Bukkit.getPlayer(session.getPlayerEntity().getUsername()) == null) { + Player bukkitPlayer; + if ((this.isLegacy && !this.isViaVersion) + || session.getPlayerEntity() == null + || (bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) { return BlockTranslator.AIR; } + World world = bukkitPlayer.getWorld(); if (isLegacy) { - return getLegacyBlock(session, x, y, z, isViaVersion); + return getLegacyBlock(session, x, y, z, true); } //TODO possibly: detect server version for all versions and use ViaVersion for block state mappings like below - return BlockTranslator.getJavaIdBlockMap().getOrDefault(Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld().getBlockAt(x, y, z).getBlockData().getAsString(), 0); + return BlockTranslator.getJavaIdBlockMap().getOrDefault(world.getBlockAt(x, y, z).getBlockData().getAsString(), 0); + } + + public static int getLegacyBlock(GeyserSession session, int x, int y, int z, boolean isViaVersion) { + if (isViaVersion) { + return getLegacyBlock(Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld(), x, y, z, true); + } else { + return BlockTranslator.AIR; + } } @SuppressWarnings("deprecation") - public static int getLegacyBlock(GeyserSession session, int x, int y, int z, boolean isViaVersion) { + public static int getLegacyBlock(World world, int x, int y, int z, boolean isViaVersion) { if (isViaVersion) { - Block block = Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld().getBlockAt(x, y, z); + Block block = world.getBlockAt(x, y, z); // Black magic that gets the old block state ID int oldBlockId = (block.getType().getId() << 4) | (block.getData() & 0xF); // Convert block state from old version -> 1.13 -> 1.13.1 -> 1.14 -> 1.15 -> 1.16 -> 1.16.2 @@ -124,6 +135,42 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { } } + @Override + public void getBlocksInSection(GeyserSession session, int x, int y, int z, Chunk chunk) { + Player bukkitPlayer; + if ((this.isLegacy && !this.isViaVersion) + || session.getPlayerEntity() == null + || (bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) { + return; + } + World world = bukkitPlayer.getWorld(); + if (this.isLegacy) { + for (int blockY = 0; blockY < 16; blockY++) { // Cache-friendly iteration order + for (int blockZ = 0; blockZ < 16; blockZ++) { + for (int blockX = 0; blockX < 16; blockX++) { + chunk.set(blockX, blockY, blockZ, getLegacyBlock(world, (x << 4) + blockX, (y << 4) + blockY, (z << 4) + blockZ, true)); + } + } + } + } else { + //TODO: see above TODO in getBlockAt + for (int blockY = 0; blockY < 16; blockY++) { // Cache-friendly iteration order + for (int blockZ = 0; blockZ < 16; blockZ++) { + for (int blockX = 0; blockX < 16; blockX++) { + Block block = world.getBlockAt((x << 4) + blockX, (y << 4) + blockY, (z << 4) + blockZ); + int id = BlockTranslator.getJavaIdBlockMap().getOrDefault(block.getBlockData().getAsString(), 0); + chunk.set(blockX, blockY, blockZ, id); + } + } + } + } + } + + @Override + public boolean hasMoreBlockDataThanChunkCache() { + return true; + } + @Override @SuppressWarnings("deprecation") public int[] getBiomeDataAt(GeyserSession session, int x, int z) { diff --git a/connector/pom.xml b/connector/pom.xml index 741acee5..5df52556 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -111,7 +111,7 @@ com.github.steveice10 mcprotocollib - 976c2d0f89 + 3a69a0614c compile 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 14825b71..7bf84b8d 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 @@ -32,7 +32,7 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import org.geysermc.connector.bootstrap.GeyserBootstrap; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.block.BlockTranslator; -import org.geysermc.connector.network.translators.world.chunk.ChunkPosition; +import org.geysermc.connector.utils.MathUtils; public class ChunkCache { @@ -48,27 +48,31 @@ public class ChunkCache { } } - public void addToCache(Column chunk) { + public Column addToCache(Column chunk) { if (!cache) { - return; + return chunk; } - long chunkPosition = ChunkPosition.toLong(chunk.getX(), chunk.getZ()); + long chunkPosition = MathUtils.chunkPositionToLong(chunk.getX(), chunk.getZ()); Column existingChunk; - if (chunk.getBiomeData() != null // Only consider merging columns if the new chunk isn't a full chunk + if (chunk.getBiomeData() == null // Only consider merging columns if the new chunk isn't a full chunk && (existingChunk = chunks.getOrDefault(chunkPosition, null)) != null) { // Column is already present in cache, we can merge with existing + boolean changed = false; for (int i = 0; i < chunk.getChunks().length; i++) { // The chunks member is final, so chunk.getChunks() will probably be inlined and then completely optimized away if (chunk.getChunks()[i] != null) { existingChunk.getChunks()[i] = chunk.getChunks()[i]; + changed = true; } } + return changed ? existingChunk : null; } else { chunks.put(chunkPosition, chunk); + return chunk; } } public Column getChunk(int chunkX, int chunkZ) { - long chunkPosition = ChunkPosition.toLong(chunkX, chunkZ); + long chunkPosition = MathUtils.chunkPositionToLong(chunkX, chunkZ); return chunks.getOrDefault(chunkPosition, null); } @@ -111,7 +115,7 @@ public class ChunkCache { return; } - long chunkPosition = ChunkPosition.toLong(chunkX, chunkZ); + long chunkPosition = MathUtils.chunkPositionToLong(chunkX, chunkZ); chunks.remove(chunkPosition); } } 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 ddd5e004..cd1a321c 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 @@ -25,6 +25,7 @@ package org.geysermc.connector.network.translators.java.world; +import com.github.steveice10.mc.protocol.data.game.chunk.Column; import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerChunkDataPacket; import com.nukkitx.nbt.NBTOutputStream; import com.nukkitx.nbt.NbtMap; @@ -32,8 +33,8 @@ import com.nukkitx.nbt.NbtUtils; import com.nukkitx.network.VarInts; import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket; import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.ByteBufOutputStream; -import io.netty.buffer.Unpooled; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.BiomeTranslator; @@ -66,57 +67,69 @@ public class JavaChunkDataTranslator extends PacketTranslator { try { - // Non-full chunks don't have all the chunk data, and Bedrock won't accept that - final boolean isNonFullChunk = (packet.getColumn().getBiomeData() == null); - - ChunkUtils.ChunkData chunkData = ChunkUtils.translateToBedrock(session, packet.getColumn(), isNonFullChunk); - ByteBuf byteBuf = Unpooled.buffer(32); - ChunkSection[] sections = chunkData.sections; + ChunkUtils.ChunkData chunkData = ChunkUtils.translateToBedrock(session, mergedColumn, isNonFullChunk); + ChunkSection[] sections = chunkData.getSections(); + // Find highest section int sectionCount = sections.length - 1; - while (sectionCount >= 0 && sections[sectionCount].isEmpty()) { + while (sectionCount >= 0 && sections[sectionCount] == null) { sectionCount--; } sectionCount++; + // Estimate chunk size + int size = 0; for (int i = 0; i < sectionCount; i++) { - ChunkSection section = chunkData.sections[i]; - section.writeToNetwork(byteBuf); + ChunkSection section = sections[i]; + size += (section != null ? section : ChunkUtils.EMPTY_SECTION).estimateNetworkSize(); } + size += 256; // Biomes + size += 1; // Border blocks + size += 1; // Extra data length (always 0) + size += chunkData.getBlockEntities().length * 64; // Conservative estimate of 64 bytes per tile entity - byte[] bedrockBiome; - if (packet.getColumn().getBiomeData() == null) { - bedrockBiome = BiomeTranslator.toBedrockBiome(session.getConnector().getWorldManager().getBiomeDataAt(session, packet.getColumn().getX(), packet.getColumn().getZ())); - } else { - bedrockBiome = BiomeTranslator.toBedrockBiome(packet.getColumn().getBiomeData()); + // Allocate output buffer + ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(size); + byte[] payload; + try { + for (int i = 0; i < sectionCount; i++) { + ChunkSection section = sections[i]; + (section != null ? section : ChunkUtils.EMPTY_SECTION).writeToNetwork(byteBuf); + } + + byteBuf.writeBytes(BiomeTranslator.toBedrockBiome(mergedColumn.getBiomeData())); // Biomes - 256 bytes + byteBuf.writeByte(0); // Border blocks - Edu edition only + VarInts.writeUnsignedInt(byteBuf, 0); // extra data length, 0 for now + + // Encode tile entities into buffer + NBTOutputStream nbtStream = NbtUtils.createNetworkWriter(new ByteBufOutputStream(byteBuf)); + for (NbtMap blockEntity : chunkData.getBlockEntities()) { + nbtStream.writeTag(blockEntity); + } + + // Copy data into byte[], because the protocol lib really likes things that are s l o w + byteBuf.readBytes(payload = new byte[byteBuf.readableBytes()]); + } finally { + byteBuf.release(); // Release buffer to allow buffer pooling to be useful } - byteBuf.writeBytes(bedrockBiome); // Biomes - 256 bytes - byteBuf.writeByte(0); // Border blocks - Edu edition only - VarInts.writeUnsignedInt(byteBuf, 0); // extra data length, 0 for now - - ByteBufOutputStream stream = new ByteBufOutputStream(Unpooled.buffer()); - NBTOutputStream nbtStream = NbtUtils.createNetworkWriter(stream); - for (NbtMap blockEntity : chunkData.getBlockEntities()) { - nbtStream.writeTag(blockEntity); - } - - byteBuf.writeBytes(stream.buffer()); - - byte[] payload = new byte[byteBuf.writerIndex()]; - byteBuf.readBytes(payload); - LevelChunkPacket levelChunkPacket = new LevelChunkPacket(); levelChunkPacket.setSubChunksLength(sectionCount); levelChunkPacket.setCachingEnabled(false); - levelChunkPacket.setChunkX(packet.getColumn().getX()); - levelChunkPacket.setChunkZ(packet.getColumn().getZ()); + levelChunkPacket.setChunkX(mergedColumn.getX()); + levelChunkPacket.setChunkZ(mergedColumn.getZ()); levelChunkPacket.setData(payload); session.sendUpstreamPacket(levelChunkPacket); - - session.getChunkCache().addToCache(packet.getColumn()); } catch (Exception ex) { ex.printStackTrace(); } 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 6972d77b..2ab3c010 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,6 +25,7 @@ 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; @@ -48,6 +49,31 @@ public class GeyserWorldManager extends WorldManager { return 0; } + @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 hasMoreBlockDataThanChunkCache() { + // 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()) { 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 ba78b8f6..fec3bb33 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,6 +25,7 @@ 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; @@ -74,6 +75,27 @@ 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 has access to more block data than the chunk cache. + *

+ * Some world managers (e.g. Spigot) can provide access to block data outside of the chunk cache, and even with chunk caching disabled. This + * method provides a means to check if this manager has this capability. + * + * @return whether or not this world manager has access to more block data than the chunk cache + */ + public abstract boolean hasMoreBlockDataThanChunkCache(); + /** * Gets the biome data for the specified chunk. * diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/BlockStorage.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/BlockStorage.java index 30edf178..d8cd7520 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/BlockStorage.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/BlockStorage.java @@ -29,14 +29,16 @@ import com.nukkitx.network.VarInts; import io.netty.buffer.ByteBuf; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; +import lombok.Getter; import org.geysermc.connector.network.translators.world.chunk.bitarray.BitArray; import org.geysermc.connector.network.translators.world.chunk.bitarray.BitArrayVersion; import java.util.function.IntConsumer; +@Getter public class BlockStorage { - private static final int SIZE = 4096; + public static final int SIZE = 4096; private final IntList palette; private BitArray bitArray; @@ -46,12 +48,12 @@ public class BlockStorage { } public BlockStorage(BitArrayVersion version) { - this.bitArray = version.createPalette(SIZE); + this.bitArray = version.createArray(SIZE); this.palette = new IntArrayList(16); this.palette.add(0); // Air is at the start of every palette. } - private BlockStorage(BitArray bitArray, IntArrayList palette) { + public BlockStorage(BitArray bitArray, IntList palette) { this.palette = palette; this.bitArray = bitArray; } @@ -64,16 +66,16 @@ public class BlockStorage { return BitArrayVersion.get(header >> 1, true); } - public synchronized int getFullBlock(int index) { + public int getFullBlock(int index) { return this.palette.getInt(this.bitArray.get(index)); } - public synchronized void setFullBlock(int index, int runtimeId) { + public void setFullBlock(int index, int runtimeId) { int idx = this.idFor(runtimeId); this.bitArray.set(index, idx); } - public synchronized void writeToNetwork(ByteBuf buffer) { + public void writeToNetwork(ByteBuf buffer) { buffer.writeByte(getPaletteHeader(bitArray.getVersion(), true)); for (int word : bitArray.getWords()) { @@ -84,8 +86,18 @@ public class BlockStorage { palette.forEach((IntConsumer) id -> VarInts.writeInt(buffer, id)); } + public int estimateNetworkSize() { + int size = 1; // Palette header + size += this.bitArray.getWords().length * 4; + + // We assume that none of the VarInts will be larger than 3 bytes + size += 3; // Palette size + size += this.palette.size() * 3; + return size; + } + private void onResize(BitArrayVersion version) { - BitArray newBitArray = version.createPalette(SIZE); + BitArray newBitArray = version.createArray(SIZE); for (int i = 0; i < SIZE; i++) { newBitArray.set(i, this.bitArray.get(i)); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/ChunkPosition.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/ChunkPosition.java deleted file mode 100644 index 9e721aa9..00000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/ChunkPosition.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.world.chunk; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.Setter; - -@Getter -@Setter -@AllArgsConstructor -public class ChunkPosition { - - /** - * Packs a chunk's X and Z coordinates into a single {@code long}. - * - * @param x the X coordinate - * @param z the Z coordinate - * @return the packed coordinates - */ - public static long toLong(int x, int z) { - return ((x & 0xFFFFFFFFL) << 32L) | (z & 0xFFFFFFFFL); - } - - private int x; - private int z; - - public Position getBlock(int x, int y, int z) { - return new Position((this.x << 4) + x, y, (this.z << 4) + z); - } - - public Position getChunkBlock(int x, int y, int z) { - int chunkX = x & 15; - int chunkY = y & 15; - int chunkZ = z & 15; - return new Position(chunkX, chunkY, chunkZ); - } - - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } else if (obj instanceof ChunkPosition) { - ChunkPosition chunkPosition = (ChunkPosition) obj; - return this.x == chunkPosition.x && this.z == chunkPosition.z; - } else { - return false; - } - } - - @Override - public int hashCode() { - return this.x * 2061811133 + this.z * 1424368303; - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/ChunkSection.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/ChunkSection.java index 48ec8806..979b79c9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/ChunkSection.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/ChunkSection.java @@ -27,42 +27,19 @@ package org.geysermc.connector.network.translators.world.chunk; import com.nukkitx.network.util.Preconditions; import io.netty.buffer.ByteBuf; -import lombok.Synchronized; public class ChunkSection { private static final int CHUNK_SECTION_VERSION = 8; - public static final int SIZE = 4096; private final BlockStorage[] storage; - private final NibbleArray blockLight; - private final NibbleArray skyLight; public ChunkSection() { - this(new BlockStorage[]{new BlockStorage(), new BlockStorage()}, new NibbleArray(SIZE), - new NibbleArray(SIZE)); + this(new BlockStorage[]{new BlockStorage(), new BlockStorage()}); } - public ChunkSection(BlockStorage[] blockStorage) { - this(blockStorage, new NibbleArray(SIZE), new NibbleArray(SIZE)); - } - - public ChunkSection(BlockStorage[] storage, byte[] blockLight, byte[] skyLight) { - Preconditions.checkNotNull(storage, "storage"); - Preconditions.checkArgument(storage.length > 1, "Block storage length must be at least 2"); - for (BlockStorage blockStorage : storage) { - Preconditions.checkNotNull(blockStorage, "storage"); - } - + public ChunkSection(BlockStorage[] storage) { this.storage = storage; - this.blockLight = new NibbleArray(blockLight); - this.skyLight = new NibbleArray(skyLight); - } - - private ChunkSection(BlockStorage[] storage, NibbleArray blockLight, NibbleArray skyLight) { - this.storage = storage; - this.blockLight = blockLight; - this.skyLight = skyLight; } public int getFullBlock(int x, int y, int z, int layer) { @@ -77,30 +54,6 @@ public class ChunkSection { this.storage[layer].setFullBlock(blockPosition(x, y, z), fullBlock); } - @Synchronized("skyLight") - public byte getSkyLight(int x, int y, int z) { - checkBounds(x, y, z); - return this.skyLight.get(blockPosition(x, y, z)); - } - - @Synchronized("skyLight") - public void setSkyLight(int x, int y, int z, byte val) { - checkBounds(x, y, z); - this.skyLight.set(blockPosition(x, y, z), val); - } - - @Synchronized("blockLight") - public byte getBlockLight(int x, int y, int z) { - checkBounds(x, y, z); - return this.blockLight.get(blockPosition(x, y, z)); - } - - @Synchronized("blockLight") - public void setBlockLight(int x, int y, int z, byte val) { - checkBounds(x, y, z); - this.blockLight.set(blockPosition(x, y, z), val); - } - public void writeToNetwork(ByteBuf buffer) { buffer.writeByte(CHUNK_SECTION_VERSION); buffer.writeByte(this.storage.length); @@ -109,12 +62,12 @@ public class ChunkSection { } } - public NibbleArray getSkyLightArray() { - return skyLight; - } - - public NibbleArray getBlockLightArray() { - return blockLight; + public int estimateNetworkSize() { + int size = 2; // Version + storage count + for (BlockStorage blockStorage : this.storage) { + size += blockStorage.estimateNetworkSize(); + } + return size; } public BlockStorage[] getBlockStorageArray() { @@ -135,7 +88,7 @@ public class ChunkSection { for (int i = 0; i < storage.length; i++) { storage[i] = this.storage[i].copy(); } - return new ChunkSection(storage, skyLight.copy(), blockLight.copy()); + return new ChunkSection(storage); } public static int blockPosition(int x, int y, int z) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/bitarray/BitArrayVersion.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/bitarray/BitArrayVersion.java index 20fa849c..47a73f7c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/bitarray/BitArrayVersion.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/bitarray/BitArrayVersion.java @@ -37,6 +37,8 @@ public enum BitArrayVersion { V2(2, 16, V3), V1(1, 32, V2); + private static final BitArrayVersion[] VALUES = values(); + final byte bits; final byte entriesPerWord; final int maxEntryValue; @@ -58,8 +60,14 @@ public enum BitArrayVersion { throw new IllegalArgumentException("Invalid palette version: " + version); } - public BitArray createPalette(int size) { - return this.createPalette(size, new int[MathUtils.ceil((float) size / entriesPerWord)]); + public static BitArrayVersion forBitsCeil(int bits) { + for (int i = VALUES.length - 1; i >= 0; i--) { + BitArrayVersion version = VALUES[i]; + if (version.bits >= bits) { + return version; + } + } + return null; } public byte getId() { @@ -78,7 +86,11 @@ public enum BitArrayVersion { return next; } - public BitArray createPalette(int size, int[] words) { + public BitArray createArray(int size) { + return this.createArray(size, new int[MathUtils.ceil((float) size / entriesPerWord)]); + } + + public BitArray createArray(int size, int[] words) { if (this == V3 || this == V5 || this == V6) { // Padded palettes aren't able to use bitwise operations due to their padding. return new PaddedBitArray(this, size, words); 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 d47584e9..a63eeb42 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java @@ -25,8 +25,10 @@ package org.geysermc.connector.utils; +import com.github.steveice10.mc.protocol.data.game.chunk.BitStorage; import com.github.steveice10.mc.protocol.data.game.chunk.Chunk; import com.github.steveice10.mc.protocol.data.game.chunk.Column; +import com.github.steveice10.mc.protocol.data.game.chunk.palette.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.StringTag; @@ -36,27 +38,39 @@ import com.nukkitx.math.vector.Vector3i; import com.nukkitx.nbt.NBTOutputStream; import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtUtils; -import com.nukkitx.protocol.bedrock.packet.*; +import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket; +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 it.unimi.dsi.fastutil.objects.ObjectArrayList; -import lombok.Getter; +import lombok.Data; +import lombok.experimental.UtilityClass; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.ItemFrameEntity; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.block.BlockStateValues; -import org.geysermc.connector.network.translators.world.block.entity.*; import org.geysermc.connector.network.translators.world.block.BlockTranslator; -import org.geysermc.connector.network.translators.world.chunk.ChunkPosition; +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.chunk.BlockStorage; import org.geysermc.connector.network.translators.world.chunk.ChunkSection; +import org.geysermc.connector.network.translators.world.chunk.bitarray.BitArray; +import org.geysermc.connector.network.translators.world.chunk.bitarray.BitArrayVersion; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Collections; +import java.util.List; -import static org.geysermc.connector.network.translators.world.block.BlockTranslator.AIR; -import static org.geysermc.connector.network.translators.world.block.BlockTranslator.BEDROCK_WATER_ID; +import static org.geysermc.connector.network.translators.world.block.BlockTranslator.*; +@UtilityClass public class ChunkUtils { /** @@ -67,6 +81,9 @@ public class ChunkUtils { private static final NbtMap EMPTY_TAG = NbtMap.builder().build(); public static final byte[] EMPTY_LEVEL_CHUNK_DATA; + public static final BlockStorage EMPTY_STORAGE = new BlockStorage(); + public static final ChunkSection EMPTY_SECTION = new ChunkSection(new BlockStorage[]{ EMPTY_STORAGE }); + static { try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { outputStream.write(new byte[258]); // Biomes + Border Size + Extra Data Size @@ -76,72 +93,144 @@ public class ChunkUtils { } EMPTY_LEVEL_CHUNK_DATA = outputStream.toByteArray(); - }catch (IOException e) { + } catch (IOException e) { throw new AssertionError("Unable to generate empty level chunk data"); } } - public static ChunkData translateToBedrock(GeyserSession session, Column column, boolean isNonFullChunk) { - ChunkData chunkData = new ChunkData(); - Chunk[] chunks = column.getChunks(); - chunkData.sections = new ChunkSection[chunks.length]; + private static int indexYZXtoXZY(int yzx) { + return (yzx >> 8) | (yzx & 0x0F0) | ((yzx & 0x00F) << 8); + } - CompoundTag[] blockEntities = column.getTileEntities(); - // Temporarily stores positions of BlockState values per chunk load - Object2IntMap blockEntityPositions = new Object2IntOpenHashMap<>(); + public static ChunkData translateToBedrock(GeyserSession session, Column column, boolean isNonFullChunk) { + Chunk[] javaSections = column.getChunks(); + ChunkSection[] sections = new ChunkSection[javaSections.length]; // Temporarily stores compound tags of Bedrock-only block entities - ObjectArrayList bedrockOnlyBlockEntities = new ObjectArrayList<>(); + List bedrockOnlyBlockEntities = Collections.emptyList(); - for (int chunkY = 0; chunkY < chunks.length; chunkY++) { - chunkData.sections[chunkY] = new ChunkSection(); - Chunk chunk = chunks[chunkY]; + BitSet waterloggedPaletteIds = new BitSet(); + BitSet pistonOrFlowerPaletteIds = new BitSet(); - // Chunk is null and caching chunks is off or this isn't a non-full chunk - if (chunk == null && (!session.getConnector().getConfig().isCacheChunks() || !isNonFullChunk)) + boolean worldManagerHasMoreBlockDataThanCache = session.getConnector().getWorldManager().hasMoreBlockDataThanChunkCache(); + + // 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()) { continue; + } - // If chunk is empty then no need to process - if (chunk != null && chunk.isEmpty()) - continue; + Palette javaPalette = javaSection.getPalette(); + IntList bedrockPalette = new IntArrayList(javaPalette.size()); + waterloggedPaletteIds.clear(); + pistonOrFlowerPaletteIds.clear(); - ChunkSection section = chunkData.sections[chunkY]; - for (int x = 0; x < 16; x++) { - for (int y = 0; y < 16; y++) { - for (int z = 0; z < 16; z++) { - int blockState; - // If a non-full chunk, then grab the block that should be here to create a 'full' chunk - if (chunk == null) { - blockState = session.getConnector().getWorldManager().getBlockAt(session, (column.getX() << 4) + x, (chunkY << 4) + y, (column.getZ() << 4) + z); - } else { - blockState = chunk.get(x, y, z); - } - int id = BlockTranslator.getBedrockBlockId(blockState); + // Iterate through palette and convert state IDs to Bedrock, doing some additional checks as we go + for (int i = 0; i < javaPalette.size(); i++) { + int javaId = javaPalette.idToState(i); + bedrockPalette.add(BlockTranslator.getBedrockBlockId(javaId)); - // Check to see if the name is in BlockTranslator.getBlockEntityString, and therefore must be handled differently - if (BlockTranslator.getBlockEntityString(blockState) != null) { - Position pos = new ChunkPosition(column.getX(), column.getZ()).getBlock(x, (chunkY << 4) + y, z); - blockEntityPositions.put(pos, blockState); - } + if (BlockTranslator.isWaterlogged(javaId)) { + waterloggedPaletteIds.set(i); + } - section.getBlockStorageArray()[0].setFullBlock(ChunkSection.blockPosition(x, y, z), id); + // Check if block is piston or flower to see if we'll need to create additional block entities, as they're only block entities in Bedrock + if (BlockStateValues.getFlowerPotValues().containsKey(javaId) || BlockStateValues.getPistonValues().containsKey(javaId)) { + pistonOrFlowerPaletteIds.set(i); + } + } - // Check if block is piston or flower - only block entities in Bedrock - if (BlockStateValues.getFlowerPotValues().containsKey(blockState) || - BlockStateValues.getPistonValues().containsKey(blockState)) { - Position pos = new ChunkPosition(column.getX(), column.getZ()).getBlock(x, (chunkY << 4) + y, z); - bedrockOnlyBlockEntities.add(BedrockOnlyBlockEntity.getTag(Vector3i.from(pos.getX(), pos.getY(), pos.getZ()), blockState)); - } + BitStorage javaData = javaSection.getStorage(); - if (BlockTranslator.isWaterlogged(blockState)) { - section.getBlockStorageArray()[1].setFullBlock(ChunkSection.blockPosition(x, y, z), BEDROCK_WATER_ID); - } + // Add Bedrock-exclusive block entities + // We only if the palette contained any blocks that are Bedrock-exclusive block entities to avoid iterating through the whole block data + // for no reason, as most sections will not contain any pistons or flower pots + if (!pistonOrFlowerPaletteIds.isEmpty()) { + bedrockOnlyBlockEntities = new ArrayList<>(); + for (int yzx = 0; yzx < BlockStorage.SIZE; yzx++) { + int paletteId = javaData.get(yzx); + if (pistonOrFlowerPaletteIds.get(paletteId)) { + bedrockOnlyBlockEntities.add(BedrockOnlyBlockEntity.getTag( + Vector3i.from((column.getX() << 4) + (yzx & 0xF), (sectionY << 4) + ((yzx >> 8) & 0xF), (column.getZ() << 4) + ((yzx >> 4) & 0xF)), + javaPalette.idToState(paletteId) + )); } } } + BitArray bedrockData = BitArrayVersion.forBitsCeil(javaData.getBitsPerEntry()).createArray(BlockStorage.SIZE); + BlockStorage layer0 = new BlockStorage(bedrockData, bedrockPalette); + BlockStorage[] layers; + + // Convert data array from YZX to XZY coordinate order + if (waterloggedPaletteIds.isEmpty()) { + // No blocks are waterlogged, simply convert coordinate order + // This could probably be optimized further... + for (int yzx = 0; yzx < BlockStorage.SIZE; yzx++) { + bedrockData.set(indexYZXtoXZY(yzx), javaData.get(yzx)); + } + + layers = new BlockStorage[]{ layer0 }; + } else { + // The section contains waterlogged blocks, we need to convert coordinate order AND generate a V1 block storage for + // layer 1 with palette ID 1 indicating water + int[] layer1Data = new int[BlockStorage.SIZE >> 5]; + for (int yzx = 0; yzx < BlockStorage.SIZE; yzx++) { + int paletteId = javaData.get(yzx); + int xzy = indexYZXtoXZY(yzx); + bedrockData.set(xzy, paletteId); + + if (waterloggedPaletteIds.get(paletteId)) { + layer1Data[xzy >> 5] |= 1 << (xzy & 0x1F); + } + } + + // V1 palette + IntList layer1Palette = new IntArrayList(2); + layer1Palette.add(0); // Air + layer1Palette.add(BEDROCK_WATER_ID); + + layers = new BlockStorage[]{ layer0, new BlockStorage(BitArrayVersion.V1.createArray(BlockStorage.SIZE, layer1Data), layer1Palette) }; + } + + sections[sectionY] = new ChunkSection(layers); } + CompoundTag[] blockEntities = column.getTileEntities(); NbtMap[] bedrockBlockEntities = new NbtMap[blockEntities.length + bedrockOnlyBlockEntities.size()]; int i = 0; while (i < blockEntities.length) { @@ -155,7 +244,7 @@ public class ChunkUtils { for (Tag subTag : tag) { if (subTag instanceof StringTag) { StringTag stringTag = (StringTag) subTag; - if (stringTag.getValue().equals("")) { + if (stringTag.getValue().isEmpty()) { tagName = stringTag.getName(); break; } @@ -169,17 +258,25 @@ public class ChunkUtils { String id = BlockEntityUtils.getBedrockBlockEntityId(tagName); BlockEntityTranslator blockEntityTranslator = BlockEntityUtils.getBlockEntityTranslator(id); Position pos = new Position((int) tag.get("x").getValue(), (int) tag.get("y").getValue(), (int) tag.get("z").getValue()); - int blockState = blockEntityPositions.getOrDefault(pos, 0); + + // Get Java blockstate ID from block entity position + int blockState = 0; + Chunk section = column.getChunks()[pos.getY() >> 4]; + if (section != null) { + blockState = section.get(pos.getX() & 0xF, pos.getY() & 0xF, pos.getZ() & 0xF); + } + bedrockBlockEntities[i] = blockEntityTranslator.getBlockEntityTag(tagName, tag, blockState); i++; } + + // Append Bedrock-exclusive block entities to output array for (NbtMap tag : bedrockOnlyBlockEntities) { bedrockBlockEntities[i] = tag; i++; } - chunkData.blockEntities = bedrockBlockEntities; - return chunkData; + return new ChunkData(sections, bedrockBlockEntities); } public static void updateChunkPosition(GeyserSession session, Vector3i position) { @@ -277,10 +374,10 @@ public class ChunkUtils { } } + @Data public static final class ChunkData { - public ChunkSection[] sections; + private final ChunkSection[] sections; - @Getter - private NbtMap[] blockEntities = new NbtMap[0]; + private final NbtMap[] blockEntities; } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/MathUtils.java b/connector/src/main/java/org/geysermc/connector/utils/MathUtils.java index 29dd2cc2..3ce4fea8 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/MathUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/MathUtils.java @@ -74,4 +74,15 @@ public class MathUtils { } return (Byte) value; } + + /** + * Packs a chunk's X and Z coordinates into a single {@code long}. + * + * @param x the X coordinate + * @param z the Z coordinate + * @return the packed coordinates + */ + public static long chunkPositionToLong(int x, int z) { + return ((x & 0xFFFFFFFFL) << 32L) | (z & 0xFFFFFFFFL); + } } From 64f223358183723c82eb30e3aaf076fbcdef530d Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Thu, 15 Oct 2020 01:54:05 -0500 Subject: [PATCH 073/205] Fix wolf collar color when it's no longer angry (Closes #1404) --- .../connector/entity/living/animal/tameable/WolfEntity.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 ab631ebe..6fe9e592 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 @@ -34,6 +34,8 @@ import org.geysermc.connector.network.session.GeyserSession; public class WolfEntity extends TameableEntity { + private byte collarColor; + public WolfEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); } @@ -57,12 +59,13 @@ public class WolfEntity extends TameableEntity { // Wolf collar color // Relies on EntityData.OWNER_EID being set in TameableEntity.java if (entityMetadata.getId() == 19 && !metadata.getFlags().getFlag(EntityFlag.ANGRY)) { - metadata.put(EntityData.COLOR, (byte) (int) entityMetadata.getValue()); + metadata.put(EntityData.COLOR, collarColor = (byte) (int) entityMetadata.getValue()); } // Wolf anger (1.16+) if (entityMetadata.getId() == 20) { metadata.getFlags().setFlag(EntityFlag.ANGRY, (int) entityMetadata.getValue() != 0); + metadata.put(EntityData.COLOR, (int) entityMetadata.getValue() != 0 ? (byte) 0 : collarColor); } super.updateBedrockMetadata(entityMetadata, session); From ae70dbeece0094814b80ed2edd9e39add53b033d Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Sat, 17 Oct 2020 23:13:04 -0400 Subject: [PATCH 074/205] JavaOpenWindowTranslator: Use MessageUtils for inventory name (#1416) * JavaOpenWindowTranslator: Use MessageUtils for inventory name * Remove important messaging --- .../java/window/JavaOpenWindowTranslator.java | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java index 820639b3..099de317 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java @@ -25,11 +25,9 @@ package org.geysermc.connector.network.translators.java.window; +import com.github.steveice10.mc.protocol.data.message.MessageSerializer; import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientCloseWindowPacket; import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerOpenWindowPacket; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; @@ -37,6 +35,7 @@ import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.network.translators.inventory.InventoryTranslator; import org.geysermc.connector.utils.InventoryUtils; import org.geysermc.connector.utils.LocaleUtils; +import org.geysermc.connector.utils.MessageUtils; @Translator(packet = ServerOpenWindowPacket.class) public class JavaOpenWindowTranslator extends PacketTranslator { @@ -58,18 +57,8 @@ public class JavaOpenWindowTranslator extends PacketTranslator Date: Sat, 17 Oct 2020 23:50:41 -0400 Subject: [PATCH 075/205] Fix mob mount positions (#1392) * Fix mob mount positions Uses offsets from Java Edition. * Fix Boat mount pos for multiple passengers and Fix Ravager Remove unnecessary horse metadata * Fix Minecart & Boat Mount Pos, Fix Player Height Offset * Use offset of EntityType.PLAYER * Add back metadata --- .../JavaEntitySetPassengersTranslator.java | 170 ++++++++++++++---- 1 file changed, 133 insertions(+), 37 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntitySetPassengersTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntitySetPassengersTranslator.java index 094d64df..64f0e3e9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntitySetPassengersTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntitySetPassengersTranslator.java @@ -82,7 +82,6 @@ public class JavaEntitySetPassengersTranslator extends PacketTranslator 1)); rider = false; } @@ -90,6 +89,9 @@ public class JavaEntitySetPassengersTranslator extends PacketTranslator 1)); + this.updateOffset(passenger, entity, session, false, false, (packet.getPassengerIds().length > 1)); + } else { + this.updateOffset(passenger, entity, session, (packet.getPassengerIds()[0] == passengerId), true, (packet.getPassengerIds().length > 1)); } // Force an update to the passenger metadata passenger.updateBedrockMetadata(session); } - if (entity.getEntityType() == EntityType.HORSE) { - entity.getMetadata().put(EntityData.RIDER_SEAT_POSITION, Vector3f.from(0.0f, 2.3200102f, -0.2f)); - entity.getMetadata().put(EntityData.RIDER_MAX_ROTATION, 181.0f); - - entity.updateBedrockMetadata(session); + switch (entity.getEntityType()) { + case HORSE: + case SKELETON_HORSE: + case DONKEY: + case MULE: + case RAVAGER: + entity.getMetadata().put(EntityData.RIDER_MAX_ROTATION, 181.0f); + entity.updateBedrockMetadata(session); + break; } } - private void updateOffset(Entity passenger, EntityType mountType, GeyserSession session, boolean rider, boolean riding, boolean moreThanOneEntity) { - // Without the Y offset, Bedrock players will find themselves in the floor when mounting - float yOffset = 0; + private float getMountedHeightOffset(Entity mount) { + final EntityType mountType = mount.getEntityType(); + float mountedHeightOffset = mountType.getHeight() * 0.75f; switch (mountType) { - case BOAT: - yOffset = passenger.getEntityType() == EntityType.PLAYER ? 1.02001f : -0.2f; - break; - case MINECART: - yOffset = passenger.getEntityType() == EntityType.PLAYER ? 1.02001f : 0f; + case CHICKEN: + case SPIDER: + mountedHeightOffset = mountType.getHeight() * 0.5f; break; case DONKEY: - yOffset = 2.1f; - break; - case HORSE: - case SKELETON_HORSE: - case ZOMBIE_HORSE: case MULE: - yOffset = 2.3f; + mountedHeightOffset -= 0.25f; break; case LLAMA: - case TRADER_LLAMA: - yOffset = 2.5f; + mountedHeightOffset = mountType.getHeight() * 0.67f; break; - case PIG: - yOffset = 1.85001f; + case MINECART: + case MINECART_HOPPER: + case MINECART_TNT: + case MINECART_CHEST: + case MINECART_FURNACE: + case MINECART_SPAWNER: + case MINECART_COMMAND_BLOCK: + mountedHeightOffset = 0; break; - case ARMOR_STAND: - yOffset = 1.3f; + case BOAT: + mountedHeightOffset = -0.1f; + break; + case HOGLIN: + case ZOGLIN: + boolean isBaby = mount.getMetadata().getFlags().getFlag(EntityFlag.BABY); + mountedHeightOffset = mountType.getHeight() - (isBaby ? 0.2f : 0.15f); + break; + case PIGLIN: + mountedHeightOffset = mountType.getHeight() * 0.92f; + break; + case RAVAGER: + mountedHeightOffset = 2.1f; + break; + case SKELETON_HORSE: + mountedHeightOffset -= 0.1875f; break; case STRIDER: - yOffset = passenger.getEntityType() == EntityType.PLAYER ? 2.8200102f : 1.6f; + mountedHeightOffset = mountType.getHeight() - 0.19f; break; } - Vector3f offset = Vector3f.from(0f, yOffset, 0f); - if (mountType == EntityType.STRIDER) { - offset = offset.add(0f, 0f, -0.2f); - } - // Without the X offset, more than one entity on a boat is stacked on top of each other - if (rider && moreThanOneEntity) { - offset = offset.add(Vector3f.from(0.2, 0, 0)); - } else if (moreThanOneEntity) { - offset = offset.add(Vector3f.from(-0.6, 0, 0)); + return mountedHeightOffset; + } + + private float getHeightOffset(Entity passenger) { + boolean isBaby; + switch (passenger.getEntityType()) { + case SKELETON: + case STRAY: + case WITHER_SKELETON: + return -0.6f; + case ARMOR_STAND: + // Armor stand isn't a marker + if (passenger.getMetadata().getFloat(EntityData.BOUNDING_BOX_HEIGHT) != 0.0f) { + return 0.1f; + } else { + return 0.0f; + } + case ENDERMITE: + case SILVERFISH: + return 0.1f; + case PIGLIN: + case PIGLIN_BRUTE: + case ZOMBIFIED_PIGLIN: + isBaby = passenger.getMetadata().getFlags().getFlag(EntityFlag.BABY); + return isBaby ? -0.05f : -0.45f; + case ZOMBIE: + isBaby = passenger.getMetadata().getFlags().getFlag(EntityFlag.BABY); + return isBaby ? 0.0f : -0.45f; + case EVOKER: + case ILLUSIONER: + case PILLAGER: + case RAVAGER: + case VINDICATOR: + case WITCH: + return -0.45f; + case PLAYER: + return -0.35f; } + return 0f; + } + + private void updateOffset(Entity passenger, Entity mount, GeyserSession session, boolean rider, boolean riding, boolean moreThanOneEntity) { passenger.getMetadata().getFlags().setFlag(EntityFlag.RIDING, riding); if (riding) { + // Without the Y offset, Bedrock players will find themselves in the floor when mounting + float mountedHeightOffset = getMountedHeightOffset(mount); + float heightOffset = getHeightOffset(passenger); + + float xOffset = 0; + float yOffset = mountedHeightOffset + heightOffset; + float zOffset = 0; + switch (mount.getEntityType()) { + case BOAT: + // Without the X offset, more than one entity on a boat is stacked on top of each other + if (rider && moreThanOneEntity) { + xOffset = 0.2f; + } else if (moreThanOneEntity) { + xOffset = -0.6f; + } + break; + case CHICKEN: + zOffset = -0.1f; + break; + case LLAMA: + zOffset = -0.3f; + break; + } + /* + * Bedrock Differences + * Zoglin & Hoglin seem to be taller in Bedrock edition + * Horses are tinier + * Players, Minecarts, and Boats have different origins + */ + if (passenger.getEntityType() == EntityType.PLAYER) { + yOffset += EntityType.PLAYER.getOffset(); + } + switch (mount.getEntityType()) { + case MINECART: + case MINECART_HOPPER: + case MINECART_TNT: + case MINECART_CHEST: + case MINECART_FURNACE: + case MINECART_SPAWNER: + case MINECART_COMMAND_BLOCK: + case BOAT: + yOffset -= mount.getEntityType().getHeight() * 0.5f; + } + Vector3f offset = Vector3f.from(xOffset, yOffset, zOffset); passenger.getMetadata().put(EntityData.RIDER_SEAT_POSITION, offset); } passenger.updateBedrockMetadata(session); From 0635605a24448ccec5b0498275a03038ba073025 Mon Sep 17 00:00:00 2001 From: DaPorkchop_ Date: Sun, 18 Oct 2020 16:59:37 +0200 Subject: [PATCH 076/205] fix chunk section decoding (#1418) * fix chunk section decoding * switch back to official MCProtocolLib --- connector/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/pom.xml b/connector/pom.xml index 5df52556..cf2b8994 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -111,7 +111,7 @@ com.github.steveice10 mcprotocollib - 3a69a0614c + 8270ec65e3 compile From 45429a9357657024da2c914cb57220fe14ee650f Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Sun, 18 Oct 2020 23:29:11 -0400 Subject: [PATCH 077/205] SettingsUtils: fix 'show coordinates' setting persistence (#1429) The boolean that toggled this was accidentally in the wrong spot. --- .../main/java/org/geysermc/connector/utils/SettingsUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java b/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java index 89e9fe67..13db4682 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java @@ -58,7 +58,7 @@ public class SettingsUtils { builder.setIcon(new FormImage(FormImage.FormImageType.PATH, "textures/ui/settings_glyph_color_2x.png")); builder.addComponent(new LabelComponent(LanguageUtils.getPlayerLocaleString("geyser.settings.title.client", language))); - builder.addComponent(new ToggleComponent(LanguageUtils.getPlayerLocaleString("geyser.settings.option.coordinates", language, session.getWorldCache().isShowCoordinates()))); + builder.addComponent(new ToggleComponent(LanguageUtils.getPlayerLocaleString("geyser.settings.option.coordinates", language), session.getWorldCache().isShowCoordinates())); if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.server")) { From 18e2a52d98dd833a362730ceb966494a4a2e5298 Mon Sep 17 00:00:00 2001 From: DaPorkchop_ Date: Mon, 19 Oct 2020 10:43:51 +0200 Subject: [PATCH 078/205] fix decoding sections with duplicate palette entries (#1430) --- connector/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/pom.xml b/connector/pom.xml index cf2b8994..8615ad80 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -111,7 +111,7 @@ com.github.steveice10 mcprotocollib - 8270ec65e3 + 1b01b1ffef compile From b02bc33393788c59d30a8ab30f229bee07aaac12 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Mon, 19 Oct 2020 19:03:31 -0400 Subject: [PATCH 079/205] GeyserSession: Set a default value for attackSpeed (#1419) Fixes cooldowns not showing on a fresh world. --- .../org/geysermc/connector/network/session/GeyserSession.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 1a0bbfb2..79949a5f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -194,9 +194,10 @@ public class GeyserSession implements CommandSender { /** * The current attack speed of the player. Used for sending proper cooldown timings. + * Setting a default fixes cooldowns not showing up on a fresh world. */ @Setter - private double attackSpeed; + private double attackSpeed = 4.0d; /** * The time of the last hit. Used to gauge how long the cooldown is taking. * This is a session variable in order to prevent more scheduled threads than necessary. From 7f5fac38c6466b1ad33e85def64dc9c245160135 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Mon, 19 Oct 2020 19:09:16 -0400 Subject: [PATCH 080/205] Update to Adventure 4.1.1 (#1410) * Update to Adventure 4.0.0 * Update to 4.0.1 * Update again, I guess. --- connector/pom.xml | 12 ++++++------ .../item/translators/nbt/BasicItemTranslator.java | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/connector/pom.xml b/connector/pom.xml index 8615ad80..19731987 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -137,21 +137,21 @@ 2.1.3 - com.github.kyoripowered.adventure + net.kyori adventure-api - 557865caef + 4.1.1 compile - com.github.kyoripowered.adventure + net.kyori adventure-text-serializer-gson - 557865caef + 4.1.1 compile - com.github.kyoripowered.adventure + net.kyori adventure-text-serializer-legacy - 557865caef + 4.1.1 compile diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BasicItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BasicItemTranslator.java index e4556626..1d21bbfb 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BasicItemTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BasicItemTranslator.java @@ -101,7 +101,7 @@ public class BasicItemTranslator extends NbtItemStackTranslator { if (message.startsWith("§r")) { message = message.replaceFirst("§r", ""); } - Component component = TextComponent.of(message); + Component component = Component.text(message); return GsonComponentSerializer.gson().serialize(component); } From 62d984da611b6cada4a6813a0f2655001e2a3fc8 Mon Sep 17 00:00:00 2001 From: Redned Date: Mon, 19 Oct 2020 20:56:01 -0500 Subject: [PATCH 081/205] Make userAuths information more clear --- connector/src/main/resources/config.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/connector/src/main/resources/config.yml b/connector/src/main/resources/config.yml index 0602bb54..43e3e8ed 100644 --- a/connector/src/main/resources/config.yml +++ b/connector/src/main/resources/config.yml @@ -38,18 +38,18 @@ remote: # You can ignore this when not using Floodgate. floodgate-key-file: public-key.pem -## the Xbox/MCPE username is the key for the Java server auth-info -## this allows automatic configuration/login to the remote Java server -## if you are brave/stupid enough to put your Mojang account info into -## a config file +# The Xbox/Minecraft Bedrock username is the key for the Java server auth-info. +# This allows automatic configuration/login to the remote Java server. +# If you are brave enough to put your Mojang account info into a config file. +# Uncomment the lines below to enable this feature. #userAuths: -# bluerkelp2: # MCPE/Xbox username -# email: not_really_my_email_address_mr_minecrafter53267@gmail.com # Mojang account email address -# password: "this isn't really my password" +# BedrockAccountUsername: # Your Minecraft: Bedrock Edition username +# email: javaccountemail@example.com # Your Minecraft: Java Edition email +# password: javaccountpassword123 # Your Minecraft: Java Edition password # -# herpderp40300499303040503030300500293858393589: -# email: herpderp@derpherp.com -# password: dooooo +# bluerkelp2: +# email: not_really_my_email_address_mr_minecrafter53267@gmail.com +# password: "this isn't really my password" # Bedrock clients can freeze when opening up the command prompt for the first time if given a lot of commands. # Disabling this will prevent command suggestions from being sent and solve freezing for Bedrock clients. From 7f2b2e09133efa004964e807cd8e5a8a996335e2 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Fri, 23 Oct 2020 05:01:03 +0100 Subject: [PATCH 082/205] Bedrock <-> Bedrock skin display fix (#1195) * Implement partial bedrock skin fix * Fix equals method * Fix ViaVersion Co-authored-by: DoctorMacc --- .../connector/utils/SkinProvider.java | 22 ++++++++++++------- .../geysermc/connector/utils/SkinUtils.java | 12 +++++++++- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java b/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java index 7c30e48a..82fc3a3a 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java @@ -33,6 +33,7 @@ import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.network.session.GeyserSession; import javax.imageio.ImageIO; import java.awt.*; @@ -42,13 +43,7 @@ import java.net.HttpURLConnection; import java.net.URL; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; -import java.nio.file.Paths; -import java.util.Arrays; -import java.util.Base64; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.UUID; +import java.util.*; import java.util.concurrent.*; public class SkinProvider { @@ -157,10 +152,21 @@ public class SkinProvider { public static CompletableFuture requestSkinAndCape(UUID playerId, String skinUrl, String capeUrl) { return CompletableFuture.supplyAsync(() -> { long time = System.currentTimeMillis(); + String newSkinUrl = skinUrl; + + if ("steve".equals(skinUrl) || "alex".equals(skinUrl)) { + // TODO: Don't have a for loop for this? Have a proper map? + for (GeyserSession session : GeyserConnector.getInstance().getPlayers()) { + if (session.getPlayerEntity().getUuid().equals(playerId)) { + newSkinUrl = session.getClientData().getSkinId(); + break; + } + } + } CapeProvider provider = capeUrl != null ? CapeProvider.MINECRAFT : null; SkinAndCape skinAndCape = new SkinAndCape( - getOrDefault(requestSkin(playerId, skinUrl, false), EMPTY_SKIN, 5), + getOrDefault(requestSkin(playerId, newSkinUrl, false), EMPTY_SKIN, 5), getOrDefault(requestCape(capeUrl, provider, false), EMPTY_CAPE, 5) ); diff --git a/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java b/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java index 5505acdf..d65dbc81 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java @@ -251,6 +251,7 @@ public class SkinUtils { try { GameProfile.Property skinProperty = profile.getProperty("textures"); + // TODO: Remove try/catch here JsonNode skinObject = new ObjectMapper().readTree(new String(Base64.getDecoder().decode(skinProperty.getValue()), StandardCharsets.UTF_8)); JsonNode textures = skinObject.get("textures"); @@ -271,7 +272,16 @@ public class SkinUtils { GeyserConnector.getInstance().getLogger().debug("Got invalid texture data for " + profile.getName() + " " + exception.getMessage()); } // return default skin with default cape when texture data is invalid - return new GameProfileData((isAlex ? SkinProvider.EMPTY_SKIN_ALEX.getTextureUrl() : SkinProvider.EMPTY_SKIN.getTextureUrl()), SkinProvider.EMPTY_CAPE.getTextureUrl(), isAlex); + String skinUrl = isAlex ? SkinProvider.EMPTY_SKIN_ALEX.getTextureUrl() : SkinProvider.EMPTY_SKIN.getTextureUrl(); + if ("steve".equals(skinUrl) || "alex".equals(skinUrl)) { + for (GeyserSession session : GeyserConnector.getInstance().getPlayers()) { + if (session.getPlayerEntity().getUuid().equals(profile.getId())) { + skinUrl = session.getClientData().getSkinId(); + break; + } + } + } + return new GameProfileData(skinUrl, SkinProvider.EMPTY_CAPE.getTextureUrl(), isAlex); } } } From ee8c718c621be0b6c3cde594a7b45004fd70e2e9 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Fri, 23 Oct 2020 01:25:24 -0500 Subject: [PATCH 083/205] Translate emote list packet --- .../network/session/GeyserSession.java | 24 +++++++++++ .../bedrock/BedrockEmoteListTranslator.java | 40 +++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockEmoteListTranslator.java diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 79949a5f..6b0f73ae 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -275,6 +275,10 @@ public class GeyserSession implements CommandSender { @Setter private String lastSignMessage; + @Setter + private List selectedEmotes = new ArrayList<>(); + private final Set emotes = new HashSet<>(); + private MinecraftProtocol protocol; public GeyserSession(GeyserConnector connector, BedrockServerSession bedrockServerSession) { @@ -295,6 +299,8 @@ public class GeyserSession implements CommandSender { this.inventoryCache.getInventories().put(0, inventory); + connector.getPlayers().forEach(player -> this.emotes.addAll(player.getEmotes())); + bedrockServerSession.addDisconnectHandler(disconnectReason -> { connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.disconnect", bedrockServerSession.getAddress().getAddress(), disconnectReason)); @@ -769,4 +775,22 @@ public class GeyserSession implements CommandSender { adventureSettingsPacket.getSettings().addAll(flags); sendUpstreamPacket(adventureSettingsPacket); } + + public void refreshEmotes(List emotes) { + this.selectedEmotes = emotes; + this.emotes.addAll(emotes); + for (GeyserSession player : connector.getPlayers()) { + List pieces = new ArrayList<>(); + for (UUID piece : emotes) { + if (!player.getEmotes().contains(piece)) { + this.emotes.add(piece); + } + pieces.add(piece); + } + EmoteListPacket emoteList = new EmoteListPacket(); + emoteList.setRuntimeEntityId(player.getPlayerEntity().getGeyserId()); + emoteList.getPieceIds().addAll(pieces); + player.sendUpstreamPacket(emoteList); + } + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockEmoteListTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockEmoteListTranslator.java new file mode 100644 index 00000000..ef5b1a56 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockEmoteListTranslator.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.bedrock; + +import com.nukkitx.protocol.bedrock.packet.EmoteListPacket; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; + +@Translator(packet = EmoteListPacket.class) +public class BedrockEmoteListTranslator extends PacketTranslator { + + @Override + public void translate(EmoteListPacket packet, GeyserSession session) { + session.refreshEmotes(packet.getPieceIds()); + } +} From dfba278f4d16acdef839af431645bcb70182c142 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Fri, 23 Oct 2020 01:36:34 -0500 Subject: [PATCH 084/205] Use correct methods in refreshEmotes --- .../org/geysermc/connector/network/session/GeyserSession.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 6b0f73ae..bb7602f3 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -783,9 +783,9 @@ public class GeyserSession implements CommandSender { List pieces = new ArrayList<>(); for (UUID piece : emotes) { if (!player.getEmotes().contains(piece)) { - this.emotes.add(piece); + pieces.add(piece); } - pieces.add(piece); + player.getEmotes().add(piece); } EmoteListPacket emoteList = new EmoteListPacket(); emoteList.setRuntimeEntityId(player.getPlayerEntity().getGeyserId()); From c30cb78e74921c611f3babfdcc8f6b96aa449232 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Sat, 24 Oct 2020 23:33:49 +0100 Subject: [PATCH 085/205] Add statistics menu (#1424) * Add statistics menu * Changed back button text * Add check to make sure the player requested the statistics display * Better item translation support; misc changes * Clean up session getting? * Remove extra debug that is likely unnecessary * Remove unused function * Update languages submodule * Clean up javadoc comment * Fix typo Co-authored-by: DoctorMacc Co-authored-by: Camotoy <20743703+DoctorMacc@users.noreply.github.com> --- .../common/window/CustomFormWindow.java | 4 +- .../common/window/SimpleFormWindow.java | 4 +- .../connector/command/CommandManager.java | 1 + .../command/defaults/StatisticsCommand.java | 69 ++++++ .../network/UpstreamPacketHandler.java | 5 + .../network/session/GeyserSession.java | 23 ++ .../java/JavaStatisticsTranslator.java | 46 ++++ .../world/block/BlockTranslator.java | 11 + .../connector/utils/StatisticsUtils.java | 233 ++++++++++++++++++ connector/src/main/resources/languages | 2 +- 10 files changed, 393 insertions(+), 5 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/command/defaults/StatisticsCommand.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/java/JavaStatisticsTranslator.java create mode 100644 connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java diff --git a/common/src/main/java/org/geysermc/common/window/CustomFormWindow.java b/common/src/main/java/org/geysermc/common/window/CustomFormWindow.java index efc71ae8..045552b6 100644 --- a/common/src/main/java/org/geysermc/common/window/CustomFormWindow.java +++ b/common/src/main/java/org/geysermc/common/window/CustomFormWindow.java @@ -92,7 +92,7 @@ public class CustomFormWindow extends FormWindow { } public void setResponse(String data) { - if (data == null || data.equalsIgnoreCase("null") || data.isEmpty()) { + if (data == null || data.trim().equalsIgnoreCase("null") || data.isEmpty()) { closed = true; return; } @@ -108,7 +108,7 @@ public class CustomFormWindow extends FormWindow { List componentResponses = new ArrayList<>(); try { - componentResponses = new ObjectMapper().readValue(data, new TypeReference>(){}); + componentResponses = new ObjectMapper().readValue(data.trim(), new TypeReference>(){}); } catch (IOException e) { } for (String response : componentResponses) { diff --git a/common/src/main/java/org/geysermc/common/window/SimpleFormWindow.java b/common/src/main/java/org/geysermc/common/window/SimpleFormWindow.java index 7c1acc26..3101f5fb 100644 --- a/common/src/main/java/org/geysermc/common/window/SimpleFormWindow.java +++ b/common/src/main/java/org/geysermc/common/window/SimpleFormWindow.java @@ -72,14 +72,14 @@ public class SimpleFormWindow extends FormWindow { } public void setResponse(String data) { - if (data == null || data.equalsIgnoreCase("null")) { + if (data == null || data.trim().equalsIgnoreCase("null")) { closed = true; return; } int buttonID; try { - buttonID = Integer.parseInt(data); + buttonID = Integer.parseInt(data.trim()); } catch (Exception ex) { return; } diff --git a/connector/src/main/java/org/geysermc/connector/command/CommandManager.java b/connector/src/main/java/org/geysermc/connector/command/CommandManager.java index afa75503..2b35424a 100644 --- a/connector/src/main/java/org/geysermc/connector/command/CommandManager.java +++ b/connector/src/main/java/org/geysermc/connector/command/CommandManager.java @@ -52,6 +52,7 @@ public abstract class CommandManager { registerCommand(new OffhandCommand(connector, "offhand", LanguageUtils.getLocaleStringLog("geyser.commands.offhand.desc"), "geyser.command.offhand")); registerCommand(new DumpCommand(connector, "dump", LanguageUtils.getLocaleStringLog("geyser.commands.dump.desc"), "geyser.command.dump")); registerCommand(new VersionCommand(connector, "version", LanguageUtils.getLocaleStringLog("geyser.commands.version.desc"), "geyser.command.version")); + registerCommand(new StatisticsCommand(connector, "statistics", LanguageUtils.getLocaleStringLog("geyser.commands.statistics.desc"), "geyser.command.statistics")); } public void registerCommand(GeyserCommand command) { diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/StatisticsCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/StatisticsCommand.java new file mode 100644 index 00000000..ed9db58f --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/StatisticsCommand.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.command.defaults; + +import com.github.steveice10.mc.protocol.data.game.ClientRequest; +import com.github.steveice10.mc.protocol.packet.ingame.client.ClientRequestPacket; +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.command.CommandSender; +import org.geysermc.connector.command.GeyserCommand; +import org.geysermc.connector.network.session.GeyserSession; + +public class StatisticsCommand extends GeyserCommand { + + private final GeyserConnector connector; + + public StatisticsCommand(GeyserConnector connector, String name, String description, String permission) { + super(name, description, permission); + + this.connector = connector; + } + + @Override + public void execute(CommandSender sender, String[] args) { + if (sender.isConsole()) { + return; + } + + // Make sure the sender is a Bedrock edition client + GeyserSession session = null; + if (sender instanceof GeyserSession) { + session = (GeyserSession) sender; + } else { + // Needed for Spigot - sender is not an instance of GeyserSession + for (GeyserSession otherSession : connector.getPlayers()) { + if (sender.getName().equals(otherSession.getPlayerEntity().getUsername())) { + session = otherSession; + break; + } + } + } + if (session == null) return; + session.setWaitingForStatistics(true); + ClientRequestPacket clientRequestPacket = new ClientRequestPacket(ClientRequest.STATS); + session.sendDownstreamPacket(clientRequestPacket); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java index f76d64ed..f99abbe5 100644 --- a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java @@ -40,6 +40,7 @@ import org.geysermc.connector.utils.MathUtils; import org.geysermc.connector.utils.ResourcePack; import org.geysermc.connector.utils.ResourcePackManifest; import org.geysermc.connector.utils.SettingsUtils; +import org.geysermc.connector.utils.StatisticsUtils; import java.io.FileInputStream; import java.io.InputStream; @@ -141,6 +142,10 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { public boolean handle(ModalFormResponsePacket packet) { if (packet.getFormId() == SettingsUtils.SETTINGS_FORM_ID) { return SettingsUtils.handleSettingsForm(session, packet.getFormData()); + } else if (packet.getFormId() == StatisticsUtils.STATISTICS_MENU_FORM_ID) { + return StatisticsUtils.handleMenuForm(session, packet.getFormData()); + } else if (packet.getFormId() == StatisticsUtils.STATISTICS_LIST_FORM_ID) { + return StatisticsUtils.handleListForm(session, packet.getFormData()); } return LoginEncryptionUtils.authenticateFromForm(session, connector, packet.getFormId(), packet.getFormData()); diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index bb7602f3..a8a4adb9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -32,6 +32,7 @@ import com.github.steveice10.mc.protocol.MinecraftConstants; import com.github.steveice10.mc.protocol.MinecraftProtocol; import com.github.steveice10.mc.protocol.data.SubProtocol; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; +import com.github.steveice10.mc.protocol.data.game.statistic.Statistic; import com.github.steveice10.mc.protocol.data.game.window.VillagerTrade; import com.github.steveice10.mc.protocol.data.message.MessageSerializer; import com.github.steveice10.mc.protocol.packet.handshake.client.HandshakePacket; @@ -55,6 +56,7 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2LongMap; import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap; import lombok.Getter; +import lombok.NonNull; import lombok.Setter; import org.geysermc.common.window.CustomFormWindow; import org.geysermc.common.window.FormWindow; @@ -275,6 +277,18 @@ public class GeyserSession implements CommandSender { @Setter private String lastSignMessage; + /** + * Stores a map of all statistics sent from the server. + * The server only sends new statistics back to us, so in order to show all statistics we need to cache existing ones. + */ + private final Map statistics = new HashMap<>(); + + /** + * Whether we're expecting statistics to be sent back to us. + */ + @Setter + private boolean waitingForStatistics = false; + @Setter private List selectedEmotes = new ArrayList<>(); private final Set emotes = new HashSet<>(); @@ -776,6 +790,15 @@ public class GeyserSession implements CommandSender { sendUpstreamPacket(adventureSettingsPacket); } + /** + * Used for updating statistic values since we only get changes from the server + * + * @param statistics Updated statistics values + */ + public void updateStatistics(@NonNull Map statistics) { + this.statistics.putAll(statistics); + } + public void refreshEmotes(List emotes) { this.selectedEmotes = emotes; this.emotes.addAll(emotes); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaStatisticsTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaStatisticsTranslator.java new file mode 100644 index 00000000..9a80254b --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaStatisticsTranslator.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.java; + +import com.github.steveice10.mc.protocol.packet.ingame.server.ServerStatisticsPacket; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; +import org.geysermc.connector.utils.StatisticsUtils; + +@Translator(packet = ServerStatisticsPacket.class) +public class JavaStatisticsTranslator extends PacketTranslator { + + @Override + public void translate(ServerStatisticsPacket packet, GeyserSession session) { + session.updateStatistics(packet.getStatistics()); + + if (session.isWaitingForStatistics()) { + session.setWaitingForStatistics(false); + session.sendForm(StatisticsUtils.buildMenuForm(session), StatisticsUtils.STATISTICS_MENU_FORM_ID); + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java index e5f8d6aa..5314292a 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java @@ -67,6 +67,11 @@ public class BlockTranslator { public static final Int2BooleanMap JAVA_RUNTIME_ID_TO_CAN_HARVEST_WITH_HAND = new Int2BooleanOpenHashMap(); public static final Int2ObjectMap JAVA_RUNTIME_ID_TO_TOOL_TYPE = new Int2ObjectOpenHashMap<>(); + /** + * Java numeric ID to java unique identifier, used for block names in the statistics screen + */ + public static final Int2ObjectMap JAVA_ID_TO_JAVA_IDENTIFIER_MAP = new Int2ObjectOpenHashMap<>(); + /** * Runtime command block ID, used for fixing command block minecart appearances */ @@ -124,6 +129,7 @@ public class BlockTranslator { int furnaceRuntimeId = -1; int furnaceLitRuntimeId = -1; int spawnerRuntimeId = -1; + int uniqueJavaId = -1; Iterator> blocksIterator = blocks.fields(); while (blocksIterator.hasNext()) { javaRuntimeId++; @@ -166,6 +172,11 @@ public class BlockTranslator { String cleanJavaIdentifier = entry.getKey().split("\\[")[0]; + if (!JAVA_ID_TO_JAVA_IDENTIFIER_MAP.containsValue(cleanJavaIdentifier)) { + uniqueJavaId++; + JAVA_ID_TO_JAVA_IDENTIFIER_MAP.put(uniqueJavaId, cleanJavaIdentifier); + } + if (!cleanJavaIdentifier.equals(bedrockIdentifier)) { JAVA_TO_BEDROCK_IDENTIFIERS.put(cleanJavaIdentifier, bedrockIdentifier); } diff --git a/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java b/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java new file mode 100644 index 00000000..3c42182d --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.utils; + +import com.github.steveice10.mc.protocol.data.MagicValues; +import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType; +import com.github.steveice10.mc.protocol.data.game.statistic.*; +import org.geysermc.common.window.SimpleFormWindow; +import org.geysermc.common.window.button.FormButton; +import org.geysermc.common.window.response.SimpleFormResponse; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.item.ItemRegistry; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; + +import java.util.Map; + +public class StatisticsUtils { + + // Used in UpstreamPacketHandler.java + public static final int STATISTICS_MENU_FORM_ID = 1339; + public static final int STATISTICS_LIST_FORM_ID = 1340; + + /** + * Build a form for the given session with all statistic categories + * + * @param session The session to build the form for + */ + public static SimpleFormWindow buildMenuForm(GeyserSession session) { + // Cache the language for cleaner access + String language = session.getClientData().getLanguageCode(); + + SimpleFormWindow window = new SimpleFormWindow(LocaleUtils.getLocaleString("gui.stats", language), ""); + + window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.generalButton", language))); + + window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.mined", language))); + window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.broken", language))); + window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.crafted", language))); + window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.used", language))); + window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.picked_up", language))); + window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.dropped", language))); + + window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.mobsButton", language) + " - " + LanguageUtils.getPlayerLocaleString("geyser.statistics.killed", language))); + window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.mobsButton", language) + " - " + LanguageUtils.getPlayerLocaleString("geyser.statistics.killed_by", language))); + + return window; + } + + /** + * Handle the menu form response + * + * @param session The session that sent the response + * @param response The response string to parse + * @return True if the form was parsed correctly, false if not + */ + public static boolean handleMenuForm(GeyserSession session, String response) { + SimpleFormWindow menuForm = (SimpleFormWindow) session.getWindowCache().getWindows().get(STATISTICS_MENU_FORM_ID); + menuForm.setResponse(response); + SimpleFormResponse formResponse = (SimpleFormResponse) menuForm.getResponse(); + + // Cache the language for cleaner access + String language = session.getClientData().getLanguageCode(); + + if (formResponse != null && formResponse.getClickedButton() != null) { + String title; + StringBuilder content = new StringBuilder(); + + switch (formResponse.getClickedButtonId()) { + case 0: + title = LocaleUtils.getLocaleString("stat.generalButton", language); + + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof GenericStatistic) { + content.append(LocaleUtils.getLocaleString("stat.minecraft." + ((GenericStatistic) entry.getKey()).name().toLowerCase(), language) + ": " + entry.getValue() + "\n"); + } + } + break; + case 1: + title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.mined", language); + + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof BreakBlockStatistic) { + String block = BlockTranslator.JAVA_ID_TO_JAVA_IDENTIFIER_MAP.get(((BreakBlockStatistic) entry.getKey()).getId()); + block = block.replace("minecraft:", "block.minecraft."); + block = LocaleUtils.getLocaleString(block, language); + content.append(block + ": " + entry.getValue() + "\n"); + } + } + break; + case 2: + title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.broken", language); + + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof BreakItemStatistic) { + String item = ItemRegistry.ITEM_ENTRIES.get(((BreakItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); + content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n"); + } + } + break; + case 3: + title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.crafted", language); + + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof CraftItemStatistic) { + String item = ItemRegistry.ITEM_ENTRIES.get(((CraftItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); + content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n"); + } + } + break; + case 4: + title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.used", language); + + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof UseItemStatistic) { + String item = ItemRegistry.ITEM_ENTRIES.get(((UseItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); + content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n"); + } + } + break; + case 5: + title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.picked_up", language); + + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof PickupItemStatistic) { + String item = ItemRegistry.ITEM_ENTRIES.get(((PickupItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); + content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n"); + } + } + break; + case 6: + title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.dropped", language); + + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof DropItemStatistic) { + String item = ItemRegistry.ITEM_ENTRIES.get(((DropItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); + content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n"); + } + } + break; + case 7: + title = LocaleUtils.getLocaleString("stat.mobsButton", language) + " - " + LanguageUtils.getPlayerLocaleString("geyser.statistics.killed", language); + + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof KillEntityStatistic) { + String mob = LocaleUtils.getLocaleString("entity.minecraft." + MagicValues.key(EntityType.class, ((KillEntityStatistic) entry.getKey()).getId()).name().toLowerCase(), language); + content.append(mob + ": " + entry.getValue() + "\n"); + } + } + break; + case 8: + title = LocaleUtils.getLocaleString("stat.mobsButton", language) + " - " + LanguageUtils.getPlayerLocaleString("geyser.statistics.killed_by", language); + + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof KilledByEntityStatistic) { + String mob = LocaleUtils.getLocaleString("entity.minecraft." + MagicValues.key(EntityType.class, ((KilledByEntityStatistic) entry.getKey()).getId()).name().toLowerCase(), language); + content.append(mob + ": " + entry.getValue() + "\n"); + } + } + break; + default: + return false; + } + + if (content.length() == 0) { + content = new StringBuilder(LanguageUtils.getPlayerLocaleString("geyser.statistics.none", language)); + } + + SimpleFormWindow window = new SimpleFormWindow(title, content.toString()); + window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("gui.back", language))); + session.sendForm(window, STATISTICS_LIST_FORM_ID); + } + + return true; + } + + /** + * Handle the list form response + * + * @param session The session that sent the response + * @param response The response string to parse + * @return True if the form was parsed correctly, false if not + */ + public static boolean handleListForm(GeyserSession session, String response) { + SimpleFormWindow listForm = (SimpleFormWindow) session.getWindowCache().getWindows().get(STATISTICS_LIST_FORM_ID); + listForm.setResponse(response); + + if (!listForm.isClosed()) { + session.sendForm(buildMenuForm(session), STATISTICS_MENU_FORM_ID); + } + + return true; + } + + /** + * Finds the item translation key from the Java locale. + * + * @param item the namespaced item to search for. + * @param language the language to search in + * @return the full name of the item + */ + private static String getItemTranslateKey(String item, String language) { + item = item.replace("minecraft:", "item.minecraft."); + String translatedItem = LocaleUtils.getLocaleString(item, language); + if (translatedItem.equals(item)) { + // Didn't translate; must be a block + translatedItem = LocaleUtils.getLocaleString(item.replace("item.", "block."), language); + } + return translatedItem; + } +} diff --git a/connector/src/main/resources/languages b/connector/src/main/resources/languages index 5f217922..a4125be9 160000 --- a/connector/src/main/resources/languages +++ b/connector/src/main/resources/languages @@ -1 +1 @@ -Subproject commit 5f21792264a364e32425014e0be79db93593da1e +Subproject commit a4125be98fefea6cefd43dc52ccb2ade4e70573e From d93d4d0942381046f9c0e7d63060db7b6ff1c557 Mon Sep 17 00:00:00 2001 From: David Choo Date: Mon, 26 Oct 2020 11:54:37 -0400 Subject: [PATCH 086/205] Projectile fixes (#1451) * Predict the trajectory of projectiles and add particles * Correct lingering potion gravity * Update last position on move absolute * Clean up * Add egg to ItemRegistry and update mappings --- .../connector/entity/ThrowableEntity.java | 54 +++++++++++++++++++ .../translators/item/ItemRegistry.java | 7 +++ .../entity/JavaEntityStatusTranslator.java | 25 +++++++++ connector/src/main/resources/mappings | 2 +- 4 files changed, 87 insertions(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java b/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java index d0632d97..b3632606 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java @@ -26,11 +26,65 @@ package org.geysermc.connector.entity; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.LevelEventType; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; public class ThrowableEntity extends Entity { + private Vector3f lastPosition; + private ScheduledFuture positionUpdater; + public ThrowableEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); + this.lastPosition = position; + } + + @Override + public void spawnEntity(GeyserSession session) { + super.spawnEntity(session); + positionUpdater = session.getConnector().getGeneralThreadPool().scheduleAtFixedRate(() -> { + super.moveRelative(session, motion.getX(), motion.getY(), motion.getZ(), rotation, onGround); + + if (metadata.getFlags().getFlag(EntityFlag.HAS_GRAVITY)) { + float gravity = 0.03f; // Snowball, Egg, and Ender Pearl + if (entityType == EntityType.THROWN_POTION || entityType == EntityType.LINGERING_POTION) { + gravity = 0.05f; + } else if (entityType == EntityType.THROWN_EXP_BOTTLE) { + gravity = 0.07f; + } + motion = motion.down(gravity); + } + }, 0, 50, TimeUnit.MILLISECONDS); + } + + @Override + public boolean despawnEntity(GeyserSession session) { + positionUpdater.cancel(true); + if (entityType == EntityType.THROWN_ENDERPEARL) { + LevelEventPacket particlePacket = new LevelEventPacket(); + particlePacket.setType(LevelEventType.PARTICLE_TELEPORT); + particlePacket.setPosition(position); + session.sendUpstreamPacket(particlePacket); + } + return super.despawnEntity(session); + } + + @Override + public void moveRelative(GeyserSession session, double relX, double relY, double relZ, Vector3f rotation, boolean isOnGround) { + position = lastPosition; + super.moveRelative(session, relX, relY, relZ, rotation, isOnGround); + lastPosition = position; + } + + @Override + public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) { + super.moveAbsolute(session, position, rotation, isOnGround, teleported); + lastPosition = position; } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java index 850e4e05..597c87b2 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java @@ -63,6 +63,10 @@ public class ItemRegistry { * Bucket item entry, used in BedrockInventoryTransactionTranslator.java */ public static ItemEntry BUCKET; + /** + * Egg item entry, used in JavaEntityStatusTranslator.java + */ + public static ItemEntry EGG; /** * Gold item entry, used in PiglinEntity.java */ @@ -141,6 +145,9 @@ public class ItemRegistry { case "minecraft:oak_boat": BOAT = ITEM_ENTRIES.get(itemIndex); break; + case "minecraft:egg": + EGG = ITEM_ENTRIES.get(itemIndex); + break; case "minecraft:gold_ingot": GOLD = ITEM_ENTRIES.get(itemIndex); break; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityStatusTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityStatusTranslator.java index c3fbd2e9..09d0eaa9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityStatusTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityStatusTranslator.java @@ -25,10 +25,16 @@ package org.geysermc.connector.network.translators.java.entity; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.github.steveice10.mc.protocol.data.game.world.particle.ItemParticleData; import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityStatusPacket; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.LevelEventType; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; +import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; +import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket; import com.nukkitx.protocol.bedrock.packet.SetEntityMotionPacket; import org.geysermc.connector.entity.Entity; @@ -36,6 +42,9 @@ import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; +import org.geysermc.connector.network.translators.item.ItemEntry; +import org.geysermc.connector.network.translators.item.ItemRegistry; +import org.geysermc.connector.network.translators.item.ItemTranslator; @Translator(packet = ServerEntityStatusPacket.class) public class JavaEntityStatusTranslator extends PacketTranslator { @@ -88,6 +97,22 @@ public class JavaEntityStatusTranslator extends PacketTranslator Date: Tue, 27 Oct 2020 11:02:25 +0000 Subject: [PATCH 087/205] Use new Open Collaboration maven repository --- pom.xml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index acfdc3d6..345933ac 100644 --- a/pom.xml +++ b/pom.xml @@ -42,8 +42,8 @@ https://jitpack.io - nukkitx-release-repo - https://repo.nukkitx.com/maven-releases/ + opencollab-release-repo + https://repo.opencollab.dev/maven-releases/ true @@ -52,8 +52,8 @@ - nukkitx-snapshot-repo - https://repo.nukkitx.com/maven-snapshots/ + opencollab-snapshot-repo + https://repo.opencollab.dev/maven-snapshots/ false @@ -74,13 +74,13 @@ releases - nukkitx-releases - https://repo.nukkitx.com/maven-releases + opencollab-releases + https://repo.opencollab.dev/maven-releases snapshots - nukkitx-snapshots - https://repo.nukkitx.com/maven-snapshots + opencollab-snapshots + https://repo.opencollab.dev/maven-snapshots From a2a7e99402d3f074141f7b70f49212ed720a740d Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Tue, 27 Oct 2020 18:40:00 -0400 Subject: [PATCH 088/205] GUI Improvements (#1462) - Added `GeyserCommand.isExecutableOnConsole()`. If this is set to false, the command will not appear as an option in the GUI. - Added `GeyserCommand.getSubCommands()`. If not empty, the subcommand options will now appear in the GUI. --- .gitignore | 3 +- .../standalone/gui/GeyserStandaloneGUI.java | 24 +++++++++++++--- .../connector/command/GeyserCommand.java | 28 +++++++++++++++++++ .../command/defaults/DumpCommand.java | 11 ++++++-- .../command/defaults/OffhandCommand.java | 5 ++++ .../command/defaults/StatisticsCommand.java | 5 ++++ 6 files changed, 68 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index e3c3b0a5..85f8a6e9 100644 --- a/.gitignore +++ b/.gitignore @@ -242,4 +242,5 @@ logs/ public-key.pem locales/ /cache/ -/packs/ \ No newline at end of file +/packs/ +/dump.json \ No newline at end of file diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/GeyserStandaloneGUI.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/GeyserStandaloneGUI.java index 50deeb1b..aeee8462 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/GeyserStandaloneGUI.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/GeyserStandaloneGUI.java @@ -261,14 +261,30 @@ public class GeyserStandaloneGUI { for (Map.Entry command : geyserCommandManager.getCommands().entrySet()) { // Remove the offhand command and any alias commands to prevent duplicates in the list - if ("offhand".equals(command.getValue().getName()) || command.getValue().getAliases().contains(command.getKey())) { + if (!command.getValue().isExecutableOnConsole() || command.getValue().getAliases().contains(command.getKey())) { continue; } // Create the button that runs the command - JMenuItem commandButton = new JMenuItem(command.getValue().getName()); + boolean hasSubCommands = command.getValue().hasSubCommands(); + // Add an extra menu if there are more commands that can be run + JMenuItem commandButton = hasSubCommands ? new JMenu(command.getValue().getName()) : new JMenuItem(command.getValue().getName()); commandButton.getAccessibleContext().setAccessibleDescription(command.getValue().getDescription()); - commandButton.addActionListener(e -> command.getValue().execute(geyserStandaloneLogger, new String[]{ })); + if (!hasSubCommands) { + commandButton.addActionListener(e -> command.getValue().execute(geyserStandaloneLogger, new String[]{ })); + } else { + // Add a submenu that's the same name as the menu can't be pressed + JMenuItem otherCommandButton = new JMenuItem(command.getValue().getName()); + otherCommandButton.getAccessibleContext().setAccessibleDescription(command.getValue().getDescription()); + otherCommandButton.addActionListener(e -> command.getValue().execute(geyserStandaloneLogger, new String[]{ })); + commandButton.add(otherCommandButton); + // Add a menu option for all possible subcommands + for (String subCommandName : command.getValue().getSubCommands()) { + JMenuItem item = new JMenuItem(subCommandName); + item.addActionListener(e -> command.getValue().execute(geyserStandaloneLogger, new String[]{subCommandName})); + commandButton.add(item); + } + } commandsMenu.add(commandButton); } @@ -291,7 +307,7 @@ public class GeyserStandaloneGUI { playerTableModel.getDataVector().removeAllElements(); for (GeyserSession player : GeyserConnector.getInstance().getPlayers()) { - Vector row = new Vector(); + Vector row = new Vector<>(); row.add(player.getSocketAddress().getHostName()); row.add(player.getPlayerEntity().getUsername()); diff --git a/connector/src/main/java/org/geysermc/connector/command/GeyserCommand.java b/connector/src/main/java/org/geysermc/connector/command/GeyserCommand.java index 62bc6c73..2bb8893c 100644 --- a/connector/src/main/java/org/geysermc/connector/command/GeyserCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/GeyserCommand.java @@ -30,6 +30,7 @@ import lombok.RequiredArgsConstructor; import lombok.Setter; import java.util.ArrayList; +import java.util.Collections; import java.util.List; @Getter @@ -44,4 +45,31 @@ public abstract class GeyserCommand { private List aliases = new ArrayList<>(); public abstract void execute(CommandSender sender, String[] args); + + /** + * If false, hides the command from being shown on the Geyser Standalone GUI. + * + * @return true if the command can be run on the server console + */ + public boolean isExecutableOnConsole() { + return true; + } + + /** + * Used in the GUI to know what subcommands can be run + * + * @return a list of all possible subcommands, or empty if none. + */ + public List getSubCommands() { + return Collections.emptyList(); + } + + /** + * Shortcut to {@link #getSubCommands()}{@code .isEmpty()}. + * + * @return true if there are subcommand present for this command. + */ + public boolean hasSubCommands() { + return !getSubCommands().isEmpty(); + } } \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/DumpCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/DumpCommand.java index 9ad0d23d..9103755a 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/DumpCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/DumpCommand.java @@ -27,12 +27,10 @@ package org.geysermc.connector.command.defaults; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter; -import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider; -import org.geysermc.connector.common.ChatColor; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.CommandSender; import org.geysermc.connector.command.GeyserCommand; +import org.geysermc.connector.common.ChatColor; import org.geysermc.connector.common.serializer.AsteriskSerializer; import org.geysermc.connector.dump.DumpInfo; import org.geysermc.connector.utils.LanguageUtils; @@ -40,6 +38,8 @@ import org.geysermc.connector.utils.WebUtils; import java.io.FileOutputStream; import java.io.IOException; +import java.util.Arrays; +import java.util.List; public class DumpCommand extends GeyserCommand { @@ -130,4 +130,9 @@ public class DumpCommand extends GeyserCommand { connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.commands.dump.created", sender.getName(), uploadedDumpUrl)); } } + + @Override + public List getSubCommands() { + return Arrays.asList("offline", "full"); + } } diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/OffhandCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/OffhandCommand.java index b1b60132..d5e0a792 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/OffhandCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/OffhandCommand.java @@ -68,4 +68,9 @@ public class OffhandCommand extends GeyserCommand { } } } + + @Override + public boolean isExecutableOnConsole() { + return false; + } } diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/StatisticsCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/StatisticsCommand.java index ed9db58f..52379145 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/StatisticsCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/StatisticsCommand.java @@ -66,4 +66,9 @@ public class StatisticsCommand extends GeyserCommand { ClientRequestPacket clientRequestPacket = new ClientRequestPacket(ClientRequest.STATS); session.sendDownstreamPacket(clientRequestPacket); } + + @Override + public boolean isExecutableOnConsole() { + return false; + } } From 9b46bf8bc9f08315bf2a8241857a3cfc5d955b54 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Thu, 29 Oct 2020 16:44:45 -0400 Subject: [PATCH 089/205] BedrockActionTranslator: Fix occasional death stall (#1432) Usually this happened when joining from another dimension after the player exited to the main menu on the death screen. The player would not realize that they are dead. --- .../bedrock/entity/player/BedrockActionTranslator.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java index 91caf8d1..e3bcbce9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java @@ -68,6 +68,8 @@ public class BedrockActionTranslator extends PacketTranslator Date: Thu, 29 Oct 2020 18:30:52 -0400 Subject: [PATCH 090/205] Introduce CommandSender.getLocale() (#1431) * Introduce CommandSender.getLocale() This allows Geyser-specific commands (e.g. `/geyser help`) to be displayed in the (Java or Bedrock) player's default language, which stops those commands from simply being displayed in the default locale. * Tweak Javadoc * Set CommandManager's GeyserConnector to final * Clean up --- .../command/BungeeCommandSender.java | 23 ++++-- .../command/GeyserBungeeCommandExecutor.java | 15 ++-- .../platform/spigot/GeyserSpigotPlugin.java | 4 ++ .../command/GeyserSpigotCommandExecutor.java | 13 ++-- .../spigot/command/SpigotCommandSender.java | 71 +++++++++++++++++-- .../GeyserVelocityCommandExecutor.java | 13 ++-- .../command/VelocityCommandSender.java | 25 +++++-- .../geysermc/connector/GeyserConnector.java | 2 +- .../connector/command/CommandManager.java | 18 ++--- .../connector/command/CommandSender.java | 18 +++++ .../connector/command/GeyserCommand.java | 3 + .../command/defaults/DumpCommand.java | 16 ++--- .../command/defaults/HelpCommand.java | 14 ++-- .../command/defaults/ListCommand.java | 10 ++- .../command/defaults/OffhandCommand.java | 4 +- .../command/defaults/ReloadCommand.java | 9 +-- .../command/defaults/VersionCommand.java | 10 +-- .../org/geysermc/connector/entity/Entity.java | 2 +- .../network/UpstreamPacketHandler.java | 4 +- .../network/session/GeyserSession.java | 7 +- .../network/session/cache/BossBar.java | 4 +- .../EnchantmentInventoryTranslator.java | 4 +- .../translators/item/ItemTranslator.java | 2 +- .../translators/java/JavaChatTranslator.java | 2 +- .../java/JavaDisconnectPacket.java | 2 +- .../java/JavaJoinGameTranslator.java | 2 +- .../java/JavaLoginDisconnectTranslator.java | 2 +- .../translators/java/JavaTitleTranslator.java | 2 +- .../java/scoreboard/JavaTeamTranslator.java | 8 +-- .../java/window/JavaOpenWindowTranslator.java | 4 +- .../world/JavaNotifyClientTranslator.java | 2 +- .../java/world/JavaPlayEffectTranslator.java | 2 +- .../connector/utils/LanguageUtils.java | 4 +- .../geysermc/connector/utils/LocaleUtils.java | 1 - .../connector/utils/LoginEncryptionUtils.java | 6 +- .../connector/utils/MessageUtils.java | 2 +- .../connector/utils/SettingsUtils.java | 2 +- 37 files changed, 212 insertions(+), 120 deletions(-) diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/BungeeCommandSender.java b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/BungeeCommandSender.java index d40dc902..3ad8b54f 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/BungeeCommandSender.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/BungeeCommandSender.java @@ -25,17 +25,20 @@ package org.geysermc.platform.bungeecord.command; -import lombok.AllArgsConstructor; - import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.connection.ProxiedPlayer; - import org.geysermc.connector.command.CommandSender; +import org.geysermc.connector.utils.LanguageUtils; -@AllArgsConstructor public class BungeeCommandSender implements CommandSender { - private net.md_5.bungee.api.CommandSender handle; + private final net.md_5.bungee.api.CommandSender handle; + + public BungeeCommandSender(net.md_5.bungee.api.CommandSender handle) { + this.handle = handle; + // Ensure even Java players' languages are loaded + LanguageUtils.loadGeyserLocale(getLocale()); + } @Override public String getName() { @@ -51,4 +54,14 @@ public class BungeeCommandSender implements CommandSender { public boolean isConsole() { return !(handle instanceof ProxiedPlayer); } + + @Override + public String getLocale() { + if (handle instanceof ProxiedPlayer) { + ProxiedPlayer player = (ProxiedPlayer) handle; + String locale = player.getLocale().getLanguage() + "_" + player.getLocale().getCountry(); + return LanguageUtils.formatLocale(locale); + } + return LanguageUtils.getDefaultLocale(); + } } diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/GeyserBungeeCommandExecutor.java b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/GeyserBungeeCommandExecutor.java index f673a3f5..ff7a2e3d 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/GeyserBungeeCommandExecutor.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/GeyserBungeeCommandExecutor.java @@ -27,13 +27,10 @@ package org.geysermc.platform.bungeecord.command; import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.CommandSender; -import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.plugin.Command; import net.md_5.bungee.api.plugin.TabExecutor; - import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.GeyserCommand; -import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.utils.LanguageUtils; import java.util.ArrayList; @@ -41,7 +38,7 @@ import java.util.Arrays; public class GeyserBungeeCommandExecutor extends Command implements TabExecutor { - private GeyserConnector connector; + private final GeyserConnector connector; public GeyserBungeeCommandExecutor(GeyserConnector connector) { super("geyser"); @@ -54,14 +51,10 @@ public class GeyserBungeeCommandExecutor extends Command implements TabExecutor if (args.length > 0) { if (getCommand(args[0]) != null) { if (!sender.hasPermission(getCommand(args[0]).getPermission())) { - String message = ""; - if (sender instanceof GeyserSession) { - message = LanguageUtils.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", ((GeyserSession) sender).getClientData().getLanguageCode()); - } else { - message = LanguageUtils.getLocaleStringLog("geyser.bootstrap.command.permission_fail"); - } + BungeeCommandSender commandSender = new BungeeCommandSender(sender); + String message = LanguageUtils.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", commandSender.getLocale()); - sender.sendMessage(TextComponent.fromLegacyText(ChatColor.RED + message)); + commandSender.sendMessage(ChatColor.RED + message); return; } getCommand(args[0]).execute(new BungeeCommandSender(sender), args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]); diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java index 9cc0bc06..892f8feb 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java @@ -40,6 +40,7 @@ import org.geysermc.connector.utils.FileUtils; import org.geysermc.connector.utils.LanguageUtils; import org.geysermc.platform.spigot.command.GeyserSpigotCommandExecutor; import org.geysermc.platform.spigot.command.GeyserSpigotCommandManager; +import org.geysermc.platform.spigot.command.SpigotCommandSender; import org.geysermc.platform.spigot.world.GeyserSpigotBlockPlaceListener; import org.geysermc.platform.spigot.world.GeyserSpigotWorldManager; @@ -130,6 +131,9 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { geyserLogger.debug("Legacy version of Minecraft (1.15.2 or older) detected; not using 3D biomes."); } + // Set if we need to use a different method for getting a player's locale + SpigotCommandSender.setUseLegacyLocaleMethod(!isCompatible(Bukkit.getServer().getVersion(), "1.12.0")); + this.geyserWorldManager = new GeyserSpigotWorldManager(isLegacy, use3dBiomes, isViaVersion); GeyserSpigotBlockPlaceListener blockPlaceListener = new GeyserSpigotBlockPlaceListener(connector, isLegacy, isViaVersion); diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/GeyserSpigotCommandExecutor.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/GeyserSpigotCommandExecutor.java index 2dba2901..0edb8448 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/GeyserSpigotCommandExecutor.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/GeyserSpigotCommandExecutor.java @@ -32,7 +32,6 @@ import org.bukkit.command.CommandSender; import org.bukkit.command.TabExecutor; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.GeyserCommand; -import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.utils.LanguageUtils; import java.util.ArrayList; @@ -42,21 +41,17 @@ import java.util.List; @AllArgsConstructor public class GeyserSpigotCommandExecutor implements TabExecutor { - private GeyserConnector connector; + private final GeyserConnector connector; @Override public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { if (args.length > 0) { if (getCommand(args[0]) != null) { if (!sender.hasPermission(getCommand(args[0]).getPermission())) { - String message = ""; - if (sender instanceof GeyserSession) { - message = LanguageUtils.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", ((GeyserSession) sender).getClientData().getLanguageCode()); - } else { - message = LanguageUtils.getLocaleStringLog("geyser.bootstrap.command.permission_fail"); - } + SpigotCommandSender commandSender = new SpigotCommandSender(sender); + String message = LanguageUtils.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", commandSender.getLocale());; - sender.sendMessage(ChatColor.RED + message); + commandSender.sendMessage(ChatColor.RED + message); return true; } getCommand(args[0]).execute(new SpigotCommandSender(sender), args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]); diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/SpigotCommandSender.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/SpigotCommandSender.java index 55475a30..93a50066 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/SpigotCommandSender.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/SpigotCommandSender.java @@ -25,15 +25,33 @@ package org.geysermc.platform.spigot.command; -import lombok.AllArgsConstructor; - import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.entity.Player; +import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.CommandSender; +import org.geysermc.connector.utils.LanguageUtils; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; -@AllArgsConstructor public class SpigotCommandSender implements CommandSender { - private org.bukkit.command.CommandSender handle; + /** + * Whether to use {@code Player.getLocale()} or {@code Player.spigot().getLocale()}, depending on version. + * 1.12 or greater should not use the legacy method. + */ + private static boolean USE_LEGACY_METHOD = false; + private static Method LOCALE_METHOD; + + private final org.bukkit.command.CommandSender handle; + private final String locale; + + public SpigotCommandSender(org.bukkit.command.CommandSender handle) { + this.handle = handle; + this.locale = getSpigotLocale(); + // Ensure even Java players' languages are loaded + LanguageUtils.loadGeyserLocale(locale); + } @Override public String getName() { @@ -49,4 +67,49 @@ public class SpigotCommandSender implements CommandSender { public boolean isConsole() { return handle instanceof ConsoleCommandSender; } + + @Override + public String getLocale() { + return locale; + } + + /** + * Set if we are on pre-1.12, and therefore {@code player.getLocale()} doesn't exist and we have to get + * {@code player.spigot().getLocale()}. + * + * @param useLegacyMethod if we are running pre-1.12 and therefore need to use reflection to get the player locale + */ + public static void setUseLegacyLocaleMethod(boolean useLegacyMethod) { + USE_LEGACY_METHOD = useLegacyMethod; + if (USE_LEGACY_METHOD) { + try { + //noinspection JavaReflectionMemberAccess - of course it doesn't exist; that's why we're doing it + LOCALE_METHOD = Player.Spigot.class.getMethod("getLocale"); + } catch (NoSuchMethodException e) { + GeyserConnector.getInstance().getLogger().debug("Player.Spigot.getLocale() doesn't exist? Not a big deal but if you're seeing this please report it to the developers!"); + } + } + } + + /** + * So we only have to do nasty reflection stuff once per command + * + * @return the locale of the Spigot player + */ + private String getSpigotLocale() { + if (handle instanceof Player) { + Player player = (Player) handle; + if (USE_LEGACY_METHOD) { + try { + // sigh + // This was the only option on older Spigot instances and now it's gone + return (String) LOCALE_METHOD.invoke(player.spigot()); + } catch (IllegalAccessException | InvocationTargetException ignored) { + } + } else { + return player.getLocale(); + } + } + return LanguageUtils.getDefaultLocale(); + } } diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandExecutor.java b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandExecutor.java index afd6c3bf..c329fb19 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandExecutor.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandExecutor.java @@ -27,14 +27,11 @@ package org.geysermc.platform.velocity.command; import com.velocitypowered.api.command.Command; import com.velocitypowered.api.command.CommandSource; - import lombok.AllArgsConstructor; - -import net.kyori.text.TextComponent; - -import org.geysermc.connector.common.ChatColor; import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.command.CommandSender; import org.geysermc.connector.command.GeyserCommand; +import org.geysermc.connector.common.ChatColor; import org.geysermc.connector.utils.LanguageUtils; import java.util.Arrays; @@ -42,15 +39,15 @@ import java.util.Arrays; @AllArgsConstructor public class GeyserVelocityCommandExecutor implements Command { - private GeyserConnector connector; + private final GeyserConnector connector; @Override public void execute(CommandSource source, String[] args) { if (args.length > 0) { if (getCommand(args[0]) != null) { if (!source.hasPermission(getCommand(args[0]).getPermission())) { - // Not ideal to use log here but we dont get a session - source.sendMessage(TextComponent.of(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.bootstrap.command.permission_fail"))); + CommandSender sender = new VelocityCommandSender(source); + sender.sendMessage(ChatColor.RED + LanguageUtils.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.getLocale())); return; } getCommand(args[0]).execute(new VelocityCommandSender(source), args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]); diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/VelocityCommandSender.java b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/VelocityCommandSender.java index 1b0d6f3e..3a1c4603 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/VelocityCommandSender.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/VelocityCommandSender.java @@ -28,17 +28,21 @@ package org.geysermc.platform.velocity.command; import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.proxy.ConsoleCommandSource; import com.velocitypowered.api.proxy.Player; - -import lombok.AllArgsConstructor; - import net.kyori.text.TextComponent; - import org.geysermc.connector.command.CommandSender; +import org.geysermc.connector.utils.LanguageUtils; + +import java.util.Locale; -@AllArgsConstructor public class VelocityCommandSender implements CommandSender { - private CommandSource handle; + private final CommandSource handle; + + public VelocityCommandSender(CommandSource handle) { + this.handle = handle; + // Ensure even Java players' languages are loaded + LanguageUtils.loadGeyserLocale(getLocale()); + } @Override public String getName() { @@ -59,4 +63,13 @@ public class VelocityCommandSender implements CommandSender { public boolean isConsole() { return handle instanceof ConsoleCommandSource; } + + @Override + public String getLocale() { + if (handle instanceof Player) { + Locale locale = ((Player) handle).getPlayerSettings().getLocale(); + return LanguageUtils.formatLocale(locale.getLanguage() + "_" + locale.getCountry()); + } + return LanguageUtils.getDefaultLocale(); + } } diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index afed4dfd..1d535f54 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -271,7 +271,7 @@ public class GeyserConnector { // Make a copy to prevent ConcurrentModificationException final List tmpPlayers = new ArrayList<>(players); for (GeyserSession playerSession : tmpPlayers) { - playerSession.disconnect(LanguageUtils.getPlayerLocaleString("geyser.core.shutdown.kick.message", playerSession.getClientData().getLanguageCode())); + playerSession.disconnect(LanguageUtils.getPlayerLocaleString("geyser.core.shutdown.kick.message", playerSession.getLocale())); } CompletableFuture future = CompletableFuture.runAsync(new Runnable() { diff --git a/connector/src/main/java/org/geysermc/connector/command/CommandManager.java b/connector/src/main/java/org/geysermc/connector/command/CommandManager.java index 2b35424a..7adce430 100644 --- a/connector/src/main/java/org/geysermc/connector/command/CommandManager.java +++ b/connector/src/main/java/org/geysermc/connector/command/CommandManager.java @@ -40,19 +40,19 @@ public abstract class CommandManager { @Getter private final Map commands = Collections.synchronizedMap(new HashMap<>()); - private GeyserConnector connector; + private final GeyserConnector connector; public CommandManager(GeyserConnector connector) { this.connector = connector; - registerCommand(new HelpCommand(connector, "help", LanguageUtils.getLocaleStringLog("geyser.commands.help.desc"), "geyser.command.help")); - registerCommand(new ListCommand(connector, "list", LanguageUtils.getLocaleStringLog("geyser.commands.list.desc"), "geyser.command.list")); - registerCommand(new ReloadCommand(connector, "reload", LanguageUtils.getLocaleStringLog("geyser.commands.reload.desc"), "geyser.command.reload")); - registerCommand(new StopCommand(connector, "stop", LanguageUtils.getLocaleStringLog("geyser.commands.stop.desc"), "geyser.command.stop")); - registerCommand(new OffhandCommand(connector, "offhand", LanguageUtils.getLocaleStringLog("geyser.commands.offhand.desc"), "geyser.command.offhand")); - registerCommand(new DumpCommand(connector, "dump", LanguageUtils.getLocaleStringLog("geyser.commands.dump.desc"), "geyser.command.dump")); - registerCommand(new VersionCommand(connector, "version", LanguageUtils.getLocaleStringLog("geyser.commands.version.desc"), "geyser.command.version")); - registerCommand(new StatisticsCommand(connector, "statistics", LanguageUtils.getLocaleStringLog("geyser.commands.statistics.desc"), "geyser.command.statistics")); + registerCommand(new HelpCommand(connector, "help", "geyser.commands.help.desc", "geyser.command.help")); + registerCommand(new ListCommand(connector, "list", "geyser.commands.list.desc", "geyser.command.list")); + registerCommand(new ReloadCommand(connector, "reload", "geyser.commands.reload.desc", "geyser.command.reload")); + registerCommand(new StopCommand(connector, "stop", "geyser.commands.stop.desc", "geyser.command.stop")); + registerCommand(new OffhandCommand(connector, "offhand", "geyser.commands.offhand.desc", "geyser.command.offhand")); + registerCommand(new DumpCommand(connector, "dump", "geyser.commands.dump.desc", "geyser.command.dump")); + registerCommand(new VersionCommand(connector, "version", "geyser.commands.version.desc", "geyser.command.version")); + registerCommand(new StatisticsCommand(connector, "statistics", "geyser.commands.statistics.desc", "geyser.command.statistics")); } public void registerCommand(GeyserCommand command) { diff --git a/connector/src/main/java/org/geysermc/connector/command/CommandSender.java b/connector/src/main/java/org/geysermc/connector/command/CommandSender.java index be9e3430..9c398a2a 100644 --- a/connector/src/main/java/org/geysermc/connector/command/CommandSender.java +++ b/connector/src/main/java/org/geysermc/connector/command/CommandSender.java @@ -25,6 +25,12 @@ package org.geysermc.connector.command; +import org.geysermc.connector.utils.LanguageUtils; + +/** + * Implemented on top of any class that can send a command. + * For example, it wraps around Spigot's CommandSender class. + */ public interface CommandSender { String getName(); @@ -37,5 +43,17 @@ public interface CommandSender { void sendMessage(String message); + /** + * @return true if the specified sender is from the console. + */ boolean isConsole(); + + /** + * Returns the locale of the command sender. Defaults to the default locale at {@link LanguageUtils#getDefaultLocale()}. + * + * @return the locale of the command sender. + */ + default String getLocale() { + return LanguageUtils.getDefaultLocale(); + } } diff --git a/connector/src/main/java/org/geysermc/connector/command/GeyserCommand.java b/connector/src/main/java/org/geysermc/connector/command/GeyserCommand.java index 2bb8893c..adbebd0c 100644 --- a/connector/src/main/java/org/geysermc/connector/command/GeyserCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/GeyserCommand.java @@ -38,6 +38,9 @@ import java.util.List; public abstract class GeyserCommand { protected final String name; + /** + * The description of the command - will attempt to be translated. + */ protected final String description; protected final String permission; diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/DumpCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/DumpCommand.java index 9103755a..f2c37da2 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/DumpCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/DumpCommand.java @@ -73,7 +73,7 @@ public class DumpCommand extends GeyserCommand { AsteriskSerializer.showSensitive = showSensitive; - sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.dump.collecting")); + sender.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.commands.dump.collecting", sender.getLocale())); String dumpData = ""; try { if (offlineDump) { @@ -82,7 +82,7 @@ public class DumpCommand extends GeyserCommand { dumpData = MAPPER.writeValueAsString(new DumpInfo()); } } catch (IOException e) { - sender.sendMessage(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.commands.dump.collect_error")); + sender.sendMessage(ChatColor.RED + LanguageUtils.getPlayerLocaleString("geyser.commands.dump.collect_error", sender.getLocale())); connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.commands.dump.collect_error_short"), e); return; } @@ -90,21 +90,21 @@ public class DumpCommand extends GeyserCommand { String uploadedDumpUrl = ""; if (offlineDump) { - sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.dump.writing")); + sender.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.commands.dump.writing", sender.getLocale())); try { FileOutputStream outputStream = new FileOutputStream(GeyserConnector.getInstance().getBootstrap().getConfigFolder().resolve("dump.json").toFile()); outputStream.write(dumpData.getBytes()); outputStream.close(); } catch (IOException e) { - sender.sendMessage(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.commands.dump.write_error")); + sender.sendMessage(ChatColor.RED + LanguageUtils.getPlayerLocaleString("geyser.commands.dump.write_error", sender.getLocale())); connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.commands.dump.write_error_short"), e); return; } uploadedDumpUrl = "dump.json"; } else { - sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.dump.uploading")); + sender.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.commands.dump.uploading", sender.getLocale())); String response; JsonNode responseNode; @@ -112,20 +112,20 @@ public class DumpCommand extends GeyserCommand { response = WebUtils.post(DUMP_URL + "documents", dumpData); responseNode = MAPPER.readTree(response); } catch (IOException e) { - sender.sendMessage(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.commands.dump.upload_error")); + sender.sendMessage(ChatColor.RED + LanguageUtils.getPlayerLocaleString("geyser.commands.dump.upload_error", sender.getLocale())); connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.commands.dump.upload_error_short"), e); return; } if (!responseNode.has("key")) { - sender.sendMessage(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.commands.dump.upload_error_short") + ": " + (responseNode.has("message") ? responseNode.get("message").asText() : response)); + sender.sendMessage(ChatColor.RED + LanguageUtils.getPlayerLocaleString("geyser.commands.dump.upload_error_short", sender.getLocale()) + ": " + (responseNode.has("message") ? responseNode.get("message").asText() : response)); return; } uploadedDumpUrl = DUMP_URL + responseNode.get("key").asText(); } - sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.dump.message") + " " + ChatColor.DARK_AQUA + uploadedDumpUrl); + sender.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.commands.dump.message", sender.getLocale()) + " " + ChatColor.DARK_AQUA + uploadedDumpUrl); if (!sender.isConsole()) { connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.commands.dump.created", sender.getName(), uploadedDumpUrl)); } diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/HelpCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/HelpCommand.java index 0407cf6e..268dc4b5 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/HelpCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/HelpCommand.java @@ -25,11 +25,10 @@ package org.geysermc.connector.command.defaults; -import org.geysermc.connector.common.ChatColor; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.CommandSender; import org.geysermc.connector.command.GeyserCommand; -import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.common.ChatColor; import org.geysermc.connector.utils.LanguageUtils; import java.util.Collections; @@ -52,17 +51,12 @@ public class HelpCommand extends GeyserCommand { public void execute(CommandSender sender, String[] args) { int page = 1; int maxPage = 1; - String header = ""; - - if (sender instanceof GeyserSession) { - header = LanguageUtils.getPlayerLocaleString("geyser.commands.help.header", ((GeyserSession) sender).getClientData().getLanguageCode(), page, maxPage); - } else { - header = LanguageUtils.getLocaleStringLog("geyser.commands.help.header", page, maxPage); - } + String header = LanguageUtils.getPlayerLocaleString("geyser.commands.help.header", sender.getLocale(), page, maxPage); sender.sendMessage(header); Map cmds = connector.getCommandManager().getCommands(); List commands = connector.getCommandManager().getCommands().keySet().stream().sorted().collect(Collectors.toList()); - commands.forEach(cmd -> sender.sendMessage(ChatColor.YELLOW + "/geyser " + cmd + ChatColor.WHITE + ": " + cmds.get(cmd).getDescription())); + commands.forEach(cmd -> sender.sendMessage(ChatColor.YELLOW + "/geyser " + cmd + ChatColor.WHITE + ": " + + LanguageUtils.getPlayerLocaleString(cmds.get(cmd).getDescription(), sender.getLocale()))); } } diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/ListCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/ListCommand.java index 3c78c554..255c6a9b 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/ListCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/ListCommand.java @@ -35,7 +35,7 @@ import java.util.stream.Collectors; public class ListCommand extends GeyserCommand { - private GeyserConnector connector; + private final GeyserConnector connector; public ListCommand(GeyserConnector connector, String name, String description, String permission) { super(name, description, permission); @@ -46,11 +46,9 @@ public class ListCommand extends GeyserCommand { @Override public void execute(CommandSender sender, String[] args) { String message = ""; - if (sender instanceof GeyserSession) { - message = LanguageUtils.getPlayerLocaleString("geyser.commands.list.message", ((GeyserSession) sender).getClientData().getLanguageCode(), connector.getPlayers().size(), connector.getPlayers().stream().map(GeyserSession::getName).collect(Collectors.joining(" "))); - } else { - message = LanguageUtils.getLocaleStringLog("geyser.commands.list.message", connector.getPlayers().size(), connector.getPlayers().stream().map(GeyserSession::getName).collect(Collectors.joining(" "))); - } + message = LanguageUtils.getPlayerLocaleString("geyser.commands.list.message", sender.getLocale(), + connector.getPlayers().size(), + connector.getPlayers().stream().map(GeyserSession::getName).collect(Collectors.joining(" "))); sender.sendMessage(message); } diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/OffhandCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/OffhandCommand.java index d5e0a792..4b6397f1 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/OffhandCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/OffhandCommand.java @@ -36,7 +36,7 @@ import org.geysermc.connector.network.session.GeyserSession; public class OffhandCommand extends GeyserCommand { - private GeyserConnector connector; + private final GeyserConnector connector; public OffhandCommand(GeyserConnector connector, String name, String description, String permission) { super(name, description, permission); @@ -58,7 +58,7 @@ public class OffhandCommand extends GeyserCommand { session.sendDownstreamPacket(releaseItemPacket); return; } - // Needed for Bukkit - sender is not an instance of GeyserSession + // Needed for Spigot - sender is not an instance of GeyserSession for (GeyserSession session : connector.getPlayers()) { if (sender.getName().equals(session.getPlayerEntity().getUsername())) { ClientPlayerActionPacket releaseItemPacket = new ClientPlayerActionPacket(PlayerAction.SWAP_HANDS, new Position(0,0,0), diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/ReloadCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/ReloadCommand.java index 9c24fa2b..6b2be294 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/ReloadCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/ReloadCommand.java @@ -47,17 +47,12 @@ public class ReloadCommand extends GeyserCommand { return; } - String message = ""; - if (sender instanceof GeyserSession) { - message = LanguageUtils.getPlayerLocaleString("geyser.commands.reload.message", ((GeyserSession) sender).getClientData().getLanguageCode()); - } else { - message = LanguageUtils.getLocaleStringLog("geyser.commands.reload.message"); - } + String message = LanguageUtils.getPlayerLocaleString("geyser.commands.reload.message", sender.getLocale()); sender.sendMessage(message); for (GeyserSession session : connector.getPlayers()) { - session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.commands.reload.kick", session.getClientData().getLanguageCode())); + session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.commands.reload.kick", session.getLocale())); } connector.reload(); } diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/VersionCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/VersionCommand.java index f7f62e59..562bc9fb 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/VersionCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/VersionCommand.java @@ -61,12 +61,12 @@ public class VersionCommand extends GeyserCommand { bedrockVersions = BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion(); } - sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.version.version", GeyserConnector.NAME, GeyserConnector.VERSION, MinecraftConstants.GAME_VERSION, bedrockVersions)); + sender.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.commands.version.version", sender.getLocale(), GeyserConnector.NAME, GeyserConnector.VERSION, MinecraftConstants.GAME_VERSION, bedrockVersions)); // Disable update checking in dev mode //noinspection ConstantConditions - changes in production if (!GeyserConnector.VERSION.equals("DEV")) { - sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.version.checking")); + sender.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.commands.version.checking", sender.getLocale())); try { Properties gitProp = new Properties(); gitProp.load(FileUtils.getResource("git.properties")); @@ -76,16 +76,16 @@ public class VersionCommand extends GeyserCommand { int latestBuildNum = Integer.parseInt(buildXML.replaceAll("<(\\\\)?(/)?buildNumber>", "").trim()); int buildNum = Integer.parseInt(gitProp.getProperty("git.build.number")); if (latestBuildNum == buildNum) { - sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.version.no_updates")); + sender.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.commands.version.no_updates", sender.getLocale())); } else { - sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.version.outdated", (latestBuildNum - buildNum), "http://ci.geysermc.org/")); + sender.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.commands.version.outdated", sender.getLocale(), (latestBuildNum - buildNum), "https://ci.geysermc.org/")); } } else { throw new AssertionError("buildNumber missing"); } } catch (IOException | AssertionError | NumberFormatException e) { GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.commands.version.failed"), e); - sender.sendMessage(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.commands.version.failed")); + sender.sendMessage(ChatColor.RED + LanguageUtils.getPlayerLocaleString("geyser.commands.version.failed", sender.getLocale())); } } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/Entity.java b/connector/src/main/java/org/geysermc/connector/entity/Entity.java index 5e825e89..2dfb0c04 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/Entity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/Entity.java @@ -321,7 +321,7 @@ public class Entity { Message message = (Message) entityMetadata.getValue(); if (message != null) // Always translate even if it's a TextMessage since there could be translatable parameters - metadata.put(EntityData.NAMETAG, MessageUtils.getTranslatedBedrockMessage(message, session.getClientData().getLanguageCode(), true)); + metadata.put(EntityData.NAMETAG, MessageUtils.getTranslatedBedrockMessage(message, session.getLocale(), true)); } break; case 3: // is custom name visible diff --git a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java index f99abbe5..e871b7f7 100644 --- a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java @@ -170,7 +170,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { @Override public boolean handle(SetLocalPlayerAsInitializedPacket packet) { - LanguageUtils.loadGeyserLocale(session.getClientData().getLanguageCode()); + LanguageUtils.loadGeyserLocale(session.getLocale()); if (!session.isLoggedIn() && !session.isLoggingIn() && session.getConnector().getAuthType() == AuthType.ONLINE) { // TODO it is safer to key authentication on something that won't change (UUID, not username) @@ -185,7 +185,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { @Override public boolean handle(MovePlayerPacket packet) { if (session.isLoggingIn()) { - session.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.auth.login.wait", session.getClientData().getLanguageCode())); + session.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.auth.login.wait", session.getLocale())); } return translateAndDefault(packet); diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index a8a4adb9..ac72d219 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -467,7 +467,7 @@ public class GeyserSession implements CommandSender { // as it has to be extracted from a JAR if (locale.toLowerCase().equals("en_us") && !LocaleUtils.LOCALE_MAPPINGS.containsKey("en_us")) { // This should probably be left hardcoded as it will only show for en_us clients - sendMessage("Downloading your locale (en_us) this may take some time"); + sendMessage("Loading your locale (en_us); if this isn't already downloaded, this may take some time"); } // Download and load the language for the player @@ -587,6 +587,11 @@ public class GeyserSession implements CommandSender { return false; } + @Override + public String getLocale() { + return clientData.getLanguageCode(); + } + public void sendForm(FormWindow window, int id) { windowCache.showWindow(window, id); } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/BossBar.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/BossBar.java index 68e8519c..fdc609ab 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/BossBar.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/BossBar.java @@ -58,7 +58,7 @@ public class BossBar { BossEventPacket bossEventPacket = new BossEventPacket(); bossEventPacket.setBossUniqueEntityId(entityId); bossEventPacket.setAction(BossEventPacket.Action.CREATE); - bossEventPacket.setTitle(MessageUtils.getTranslatedBedrockMessage(title, session.getClientData().getLanguageCode())); + bossEventPacket.setTitle(MessageUtils.getTranslatedBedrockMessage(title, session.getLocale())); bossEventPacket.setHealthPercentage(health); bossEventPacket.setColor(color); //ignored by client bossEventPacket.setOverlay(overlay); @@ -72,7 +72,7 @@ public class BossBar { BossEventPacket bossEventPacket = new BossEventPacket(); bossEventPacket.setBossUniqueEntityId(entityId); bossEventPacket.setAction(BossEventPacket.Action.UPDATE_NAME); - bossEventPacket.setTitle(MessageUtils.getTranslatedBedrockMessage(title, session.getClientData().getLanguageCode())); + bossEventPacket.setTitle(MessageUtils.getTranslatedBedrockMessage(title, session.getLocale())); session.sendUpstreamPacket(bossEventPacket); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/EnchantmentInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/EnchantmentInventoryTranslator.java index cbcdce10..8aa91b36 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/EnchantmentInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/EnchantmentInventoryTranslator.java @@ -199,7 +199,7 @@ public class EnchantmentInventoryTranslator extends BlockInventoryTranslator { private String toRomanNumeral(GeyserSession session, int level) { return LocaleUtils.getLocaleString("enchantment.level." + level, - session.getClientData().getLanguageCode()); + session.getLocale()); } /** @@ -261,7 +261,7 @@ public class EnchantmentInventoryTranslator extends BlockInventoryTranslator { public String toString(GeyserSession session) { return LocaleUtils.getLocaleString("enchantment.minecraft." + this.toString().toLowerCase(), - session.getClientData().getLanguageCode()); + session.getLocale()); } } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java index ef629494..f95a0ccc 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java @@ -400,7 +400,7 @@ public abstract class ItemTranslator { // Check if its a message to translate if (MessageUtils.isMessage(name)) { // Get the translated name - name = MessageUtils.getTranslatedBedrockMessage(MessageSerializer.fromString(name), session.getClientData().getLanguageCode()); + name = MessageUtils.getTranslatedBedrockMessage(MessageSerializer.fromString(name), session.getLocale()); // Add the new name tag display.put(new StringTag("Name", name)); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaChatTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaChatTranslator.java index d222d729..186aaf66 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaChatTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaChatTranslator.java @@ -59,7 +59,7 @@ public class JavaChatTranslator extends PacketTranslator { break; } - String locale = session.getClientData().getLanguageCode(); + String locale = session.getLocale(); if (packet.getMessage() instanceof TranslationMessage) { textPacket.setType(TextPacket.Type.TRANSLATION); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDisconnectPacket.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDisconnectPacket.java index 43211111..f36da367 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDisconnectPacket.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDisconnectPacket.java @@ -36,6 +36,6 @@ public class JavaDisconnectPacket extends PacketTranslator skinParts = Arrays.asList(SkinPart.values()); ClientSettingsPacket clientSettingsPacket = new ClientSettingsPacket(locale, (byte) session.getRenderDistance(), ChatVisibility.FULL, true, skinParts, HandPreference.RIGHT_HAND); session.sendDownstreamPacket(clientSettingsPacket); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginDisconnectTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginDisconnectTranslator.java index d4504226..e7486c99 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginDisconnectTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginDisconnectTranslator.java @@ -37,6 +37,6 @@ public class JavaLoginDisconnectTranslator extends PacketTranslator { @Override public void translate(ServerTitlePacket packet, GeyserSession session) { SetTitlePacket titlePacket = new SetTitlePacket(); - String locale = session.getClientData().getLanguageCode(); + String locale = session.getLocale(); switch (packet.getAction()) { case TITLE: diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaTeamTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaTeamTranslator.java index 998e5eff..c621fc1f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaTeamTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaTeamTranslator.java @@ -62,8 +62,8 @@ public class JavaTeamTranslator extends PacketTranslator { .setName(MessageUtils.getBedrockMessage(packet.getDisplayName())) .setColor(packet.getColor()) .setNameTagVisibility(packet.getNameTagVisibility()) - .setPrefix(MessageUtils.getTranslatedBedrockMessage(packet.getPrefix(), session.getClientData().getLanguageCode())) - .setSuffix(MessageUtils.getTranslatedBedrockMessage(packet.getSuffix(), session.getClientData().getLanguageCode())); + .setPrefix(MessageUtils.getTranslatedBedrockMessage(packet.getPrefix(), session.getLocale())) + .setSuffix(MessageUtils.getTranslatedBedrockMessage(packet.getSuffix(), session.getLocale())); break; case UPDATE: if (team == null) { @@ -77,8 +77,8 @@ public class JavaTeamTranslator extends PacketTranslator { team.setName(MessageUtils.getBedrockMessage(packet.getDisplayName())) .setColor(packet.getColor()) .setNameTagVisibility(packet.getNameTagVisibility()) - .setPrefix(MessageUtils.getTranslatedBedrockMessage(packet.getPrefix(), session.getClientData().getLanguageCode())) - .setSuffix(MessageUtils.getTranslatedBedrockMessage(packet.getSuffix(), session.getClientData().getLanguageCode())) + .setPrefix(MessageUtils.getTranslatedBedrockMessage(packet.getPrefix(), session.getLocale())) + .setSuffix(MessageUtils.getTranslatedBedrockMessage(packet.getSuffix(), session.getLocale())) .setUpdateType(UpdateType.UPDATE); break; case ADD_PLAYER: diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java index 099de317..2c10ded6 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java @@ -58,9 +58,9 @@ public class JavaOpenWindowTranslator extends PacketTranslator params = new ArrayList<>(); String recordString = "%item." + soundEvent.name().toLowerCase(Locale.ROOT) + ".desc"; - params.add(LocaleUtils.getLocaleString(recordString, session.getClientData().getLanguageCode())); + params.add(LocaleUtils.getLocaleString(recordString, session.getLocale())); textPacket.setParameters(params); session.sendUpstreamPacket(textPacket); } diff --git a/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java index de6796a2..06d2936e 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java @@ -59,6 +59,8 @@ public class LanguageUtils { */ public static void loadGeyserLocale(String locale) { locale = formatLocale(locale); + // Don't load the locale if it's already loaded. + if (LOCALE_MAPPINGS.containsKey(locale)) return; InputStream localeStream = GeyserConnector.class.getClassLoader().getResourceAsStream("languages/texts/" + locale + ".properties"); @@ -131,7 +133,7 @@ public class LanguageUtils { * @param locale The locale to format * @return The formatted locale */ - private static String formatLocale(String locale) { + public static String formatLocale(String locale) { try { String[] parts = locale.toLowerCase().split("_"); return parts[0] + "_" + parts[1].toUpperCase(); diff --git a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java index d1d59490..11e81eee 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java @@ -35,7 +35,6 @@ import org.geysermc.connector.GeyserConnector; import java.io.*; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.HashMap; import java.util.Iterator; import java.util.List; diff --git a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java index 4bc997bd..fe63bc91 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java @@ -160,7 +160,7 @@ public class LoginEncryptionUtils { private static int AUTH_DETAILS_FORM_ID = 1337; public static void showLoginWindow(GeyserSession session) { - String userLanguage = session.getClientData().getLanguageCode(); + String userLanguage = session.getLocale(); SimpleFormWindow window = new SimpleFormWindow(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.title", userLanguage), LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.desc", userLanguage)); window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.btn_login", userLanguage))); window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.btn_disconnect", userLanguage))); @@ -169,7 +169,7 @@ public class LoginEncryptionUtils { } public static void showLoginDetailsWindow(GeyserSession session) { - String userLanguage = session.getClientData().getLanguageCode(); + String userLanguage = session.getLocale(); CustomFormWindow window = new CustomFormBuilder(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.details.title", userLanguage)) .addComponent(new LabelComponent(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.details.desc", userLanguage))) .addComponent(new InputComponent(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.details.email", userLanguage), "account@geysermc.org", "")) @@ -210,7 +210,7 @@ public class LoginEncryptionUtils { if (response.getClickedButtonId() == 0) { showLoginDetailsWindow(session); } else if(response.getClickedButtonId() == 1) { - session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.getClientData().getLanguageCode())); + session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.getLocale())); } } else { showLoginWindow(session); diff --git a/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java b/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java index a127fd8d..b5a2bfdc 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java @@ -485,7 +485,7 @@ public class MessageUtils { */ public static boolean isTooLong(String message, GeyserSession session) { if (message.length() > 256) { - session.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.chat.too_long", session.getClientData().getLanguageCode(), message.length())); + session.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.chat.too_long", session.getLocale(), message.length())); return true; } diff --git a/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java b/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java index 13db4682..eaf71058 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java @@ -52,7 +52,7 @@ public class SettingsUtils { */ public static void buildForm(GeyserSession session) { // Cache the language for cleaner access - String language = session.getClientData().getLanguageCode(); + String language = session.getLocale(); CustomFormBuilder builder = new CustomFormBuilder(LanguageUtils.getPlayerLocaleString("geyser.settings.title.main", language)); builder.setIcon(new FormImage(FormImage.FormImageType.PATH, "textures/ui/settings_glyph_color_2x.png")); From 8fdaf6a3850b2590ce930e476d799e0e12f190af Mon Sep 17 00:00:00 2001 From: David Choo Date: Thu, 29 Oct 2020 18:35:46 -0400 Subject: [PATCH 091/205] Ender dragon Melee Attacks (#1466) * Create and position Ender Dragon Bounding Box Currently allows the player to "kill aura" target the ender dragon. * Use an entity to handle attacks for each hitbox * Use the proper flag to make entities invisible * Clean up and add some comments * Ender dragon entity metadata improvements * Add doc to segment functions * Add changes Co-authored-by: DoctorMacc --- .../living/monster/EnderDragonEntity.java | 163 ++++++++++++++++-- .../living/monster/EnderDragonPartEntity.java | 42 +++++ .../connector/entity/type/EntityType.java | 9 +- ...BedrockInventoryTransactionTranslator.java | 15 +- 4 files changed, 212 insertions(+), 17 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonPartEntity.java diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonEntity.java index aa2b4e02..14466dda 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonEntity.java @@ -32,33 +32,60 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.packet.AddEntityPacket; import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; +import lombok.Data; import org.geysermc.connector.entity.living.InsentientEntity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + public class EnderDragonEntity extends InsentientEntity { + /** + * The Ender Dragon has multiple hit boxes, which + * are each its own invisible entity + */ + private EnderDragonPartEntity head; + private EnderDragonPartEntity neck; + private EnderDragonPartEntity body; + private EnderDragonPartEntity leftWing; + private EnderDragonPartEntity rightWing; + private EnderDragonPartEntity[] tail; + + private EnderDragonPartEntity[] allParts; + + /** + * A circular buffer that stores a history of + * y and yaw values. + */ + private final Segment[] segmentHistory = new Segment[19]; + private int latestSegment = -1; + + private boolean hovering; + + private ScheduledFuture partPositionUpdater; public EnderDragonEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); + + metadata.getFlags().setFlag(EntityFlag.FIRE_IMMUNE, true); } @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + // Phase if (entityMetadata.getId() == 15) { - metadata.getFlags().setFlag(EntityFlag.FIRE_IMMUNE, true); - switch ((int) entityMetadata.getValue()) { + int value = (int) entityMetadata.getValue(); + if (value == 5) { // Performing breath attack - case 5: - EntityEventPacket entityEventPacket = new EntityEventPacket(); - entityEventPacket.setType(EntityEventType.DRAGON_FLAMING); - entityEventPacket.setRuntimeEntityId(geyserId); - entityEventPacket.setData(0); - session.sendUpstreamPacket(entityEventPacket); - case 6: - case 7: - metadata.getFlags().setFlag(EntityFlag.SITTING, true); - break; + EntityEventPacket entityEventPacket = new EntityEventPacket(); + entityEventPacket.setType(EntityEventType.DRAGON_FLAMING); + entityEventPacket.setRuntimeEntityId(geyserId); + entityEventPacket.setData(0); + session.sendUpstreamPacket(entityEventPacket); } + metadata.getFlags().setFlag(EntityFlag.SITTING, value == 5 || value == 6 || value == 7); + hovering = value == 10; } super.updateBedrockMetadata(entityMetadata, session); } @@ -81,6 +108,118 @@ public class EnderDragonEntity extends InsentientEntity { valid = true; session.sendUpstreamPacket(addEntityPacket); + head = new EnderDragonPartEntity(entityId + 1, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 1, 1); + neck = new EnderDragonPartEntity(entityId + 2, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 3, 3); + body = new EnderDragonPartEntity(entityId + 3, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 5, 3); + leftWing = new EnderDragonPartEntity(entityId + 4, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 4, 2); + rightWing = new EnderDragonPartEntity(entityId + 5, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 4, 2); + tail = new EnderDragonPartEntity[3]; + for (int i = 0; i < 3; i++) { + tail[i] = new EnderDragonPartEntity(entityId + 6 + i, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 2, 2); + } + + allParts = new EnderDragonPartEntity[]{head, neck, body, leftWing, rightWing, tail[0], tail[1], tail[2]}; + + for (EnderDragonPartEntity part : allParts) { + session.getEntityCache().spawnEntity(part); + } + + for (int i = 0; i < segmentHistory.length; i++) { + segmentHistory[i] = new Segment(); + segmentHistory[i].yaw = rotation.getZ(); + segmentHistory[i].y = position.getY(); + } + + partPositionUpdater = session.getConnector().getGeneralThreadPool().scheduleAtFixedRate(() -> { + pushSegment(); + updateBoundingBoxes(session); + }, 0, 50, TimeUnit.MILLISECONDS); + session.getConnector().getLogger().debug("Spawned entity " + entityType + " at location " + position + " with id " + geyserId + " (java id " + entityId + ")"); } + + @Override + public boolean despawnEntity(GeyserSession session) { + partPositionUpdater.cancel(true); + + for (EnderDragonPartEntity part : allParts) { + part.despawnEntity(session); + } + return super.despawnEntity(session); + } + + /** + * Updates the positions of the Ender Dragon's multiple bounding boxes + * + * @param session GeyserSession. + */ + private void updateBoundingBoxes(GeyserSession session) { + Vector3f facingDir = Vector3f.createDirectionDeg(0, rotation.getZ()); + Segment baseSegment = getSegment(5); + // Used to angle the head, neck, and tail when the dragon flies up/down + float pitch = (float) Math.toRadians(10 * (baseSegment.getY() - getSegment(10).getY())); + float pitchXZ = (float) Math.cos(pitch); + float pitchY = (float) Math.sin(pitch); + + // Lowers the head when the dragon sits/hovers + float headDuck; + if (hovering || metadata.getFlags().getFlag(EntityFlag.SITTING)) { + headDuck = -1f; + } else { + headDuck = baseSegment.y - getSegment(0).y; + } + + head.setPosition(facingDir.up(pitchY).mul(pitchXZ, 1, -pitchXZ).mul(6.5f).up(headDuck)); + neck.setPosition(facingDir.up(pitchY).mul(pitchXZ, 1, -pitchXZ).mul(5.5f).up(headDuck)); + body.setPosition(facingDir.mul(0.5f, 0f, -0.5f)); + + Vector3f wingPos = Vector3f.createDirectionDeg(0, 90f - rotation.getZ()).mul(4.5f).up(2f); + rightWing.setPosition(wingPos); + leftWing.setPosition(wingPos.mul(-1, 1, -1)); // Mirror horizontally + + Vector3f tailBase = facingDir.mul(1.5f); + for (int i = 0; i < tail.length; i++) { + float distance = (i + 1) * 2f; + // Curls the tail when the dragon turns + Segment targetSegment = getSegment(12 + 2 * i); + float angle = rotation.getZ() + targetSegment.yaw - baseSegment.yaw; + + float tailYOffset = targetSegment.y - baseSegment.y - (distance + 1.5f) * pitchY + 1.5f; + tail[i].setPosition(Vector3f.createDirectionDeg(0, angle).mul(distance).add(tailBase).mul(-pitchXZ, 1, pitchXZ).up(tailYOffset)); + } + // Send updated positions + for (EnderDragonPartEntity part : allParts) { + part.moveAbsolute(session, part.getPosition().add(position), Vector3f.ZERO, false, false); + } + } + + /** + * Store the current yaw and y into the circular buffer + */ + private void pushSegment() { + latestSegment = (latestSegment + 1) % segmentHistory.length; + segmentHistory[latestSegment].yaw = rotation.getZ(); + segmentHistory[latestSegment].y = position.getY(); + } + + /** + * Gets the previous yaw and y + * Used to curl the tail and pitch the head and tail up/down + * + * @param index Number of ticks in the past + * @return Segment with the yaw and y + */ + private Segment getSegment(int index) { + index = (latestSegment - index) % segmentHistory.length; + if (index < 0) { + index += segmentHistory.length; + } + return segmentHistory[index]; + } + + @Data + private static class Segment { + private float yaw; + private float y; + } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonPartEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonPartEntity.java new file mode 100644 index 00000000..bb5876ce --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonPartEntity.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.entity.living.monster; + +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.connector.entity.Entity; +import org.geysermc.connector.entity.type.EntityType; + +public class EnderDragonPartEntity extends Entity { + public EnderDragonPartEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation, float width, float height) { + super(entityId, geyserId, entityType, position, motion, rotation); + + metadata.put(EntityData.BOUNDING_BOX_WIDTH, width); + metadata.put(EntityData.BOUNDING_BOX_HEIGHT, height); + metadata.getFlags().setFlag(EntityFlag.INVISIBLE, true); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java index 24c15018..fddab5a4 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java +++ b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java @@ -86,7 +86,7 @@ public enum EntityType { ELDER_GUARDIAN(ElderGuardianEntity.class, 50, 1.9975f), NPC(PlayerEntity.class, 51, 1.8f, 0.6f, 0.6f, 1.62f), WITHER(WitherEntity.class, 52, 3.5f, 0.9f), - ENDER_DRAGON(EnderDragonEntity.class, 53, 4f, 13f), + ENDER_DRAGON(EnderDragonEntity.class, 53, 0f, 0f), SHULKER(ShulkerEntity.class, 54, 1f, 1f), ENDERMITE(MonsterEntity.class, 55, 0.3f, 0.4f), AGENT(Entity.class, 56, 0f), @@ -166,7 +166,12 @@ public enum EntityType { /** * Not an entity in Bedrock, so we replace it with a Pillager */ - ILLUSIONER(AbstractIllagerEntity.class, 114, 1.8f, 0.6f, 0.6f, 1.62f, "minecraft:pillager"); + ILLUSIONER(AbstractIllagerEntity.class, 114, 1.8f, 0.6f, 0.6f, 1.62f, "minecraft:pillager"), + + /** + * Not an entity in Bedrock, but used for the Ender Dragon's multiple hitboxes + */ + ENDER_DRAGON_PART(EnderDragonPartEntity.class, 32, 0, 0, 0, 0, "minecraft:armor_stand"); private static final EntityType[] VALUES = values(); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java index b81025be..b2a70146 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java @@ -49,6 +49,7 @@ import org.geysermc.connector.entity.CommandBlockMinecartEntity; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.ItemFrameEntity; import org.geysermc.connector.entity.living.merchant.AbstractMerchantEntity; +import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; @@ -251,9 +252,17 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator Date: Thu, 29 Oct 2020 18:40:42 -0400 Subject: [PATCH 092/205] Add support for more recipes (#1434) * Add support for more recipes - Tool repairing - Book cloning - Suspicious stew - Tipped arrows What still needs to be done: - Map cloning/extending (though there may be item mapping mismatch getting in the way) - Banner duplication (couldn't figure this out) * Add some more spacing * Explain recipe UUIDs --- .../translators/item/RecipeRegistry.java | 72 +++++++++++++++++-- .../java/JavaDeclareRecipesTranslator.java | 21 +++++- 2 files changed, 85 insertions(+), 8 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/RecipeRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/RecipeRegistry.java index 191b285c..411c0295 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/RecipeRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/RecipeRegistry.java @@ -34,8 +34,7 @@ import org.geysermc.connector.utils.FileUtils; import org.geysermc.connector.utils.LanguageUtils; import java.io.InputStream; -import java.util.List; -import java.util.UUID; +import java.util.*; /** * Manages any recipe-related storing @@ -46,22 +45,45 @@ public class RecipeRegistry { * A list of all possible leather armor dyeing recipes. * Created manually. */ - public static List LEATHER_DYEING_RECIPES = new ObjectArrayList<>(); + public static final List LEATHER_DYEING_RECIPES = new ObjectArrayList<>(); /** * A list of all possible firework rocket recipes, including the base rocket. * Obtained from a ProxyPass dump of protocol v407 */ - public static List FIREWORK_ROCKET_RECIPES = new ObjectArrayList<>(21); + public static final List FIREWORK_ROCKET_RECIPES = new ObjectArrayList<>(); /** * A list of all possible firework star recipes. * Obtained from a ProxyPass dump of protocol v407 */ - public static List FIREWORK_STAR_RECIPES = new ObjectArrayList<>(40); + public static final List FIREWORK_STAR_RECIPES = new ObjectArrayList<>(); /** * A list of all possible shulker box dyeing options. * Obtained from a ProxyPass dump of protocol v407 */ - public static List SHULKER_BOX_DYEING_RECIPES = new ObjectArrayList<>(); + public static final List SHULKER_BOX_DYEING_RECIPES = new ObjectArrayList<>(); + /** + * A list of all possible suspicious stew recipes. + * Obtained from a ProxyPass dump of protocol v407 + */ + public static final List SUSPICIOUS_STEW_RECIPES = new ObjectArrayList<>(); + /** + * A list of all possible tipped arrow recipes. + * Obtained from a ProxyPass dump of protocol v407 + */ + public static final List TIPPED_ARROW_RECIPES = new ObjectArrayList<>(); + + // TODO: These are the other "multi" UUIDs that supposedly enable various recipes. Find out what each enables. + // 442d85ed-8272-4543-a6f1-418f90ded05d 8b36268c-1829-483c-a0f1-993b7156a8f2 602234e4-cac1-4353-8bb7-b1ebff70024b 98c84b38-1085-46bd-b1ce-dd38c159e6cc + // d81aaeaf-e172-4440-9225-868df030d27b b5c5d105-75a2-4076-af2b-923ea2bf4bf0 00000000-0000-0000-0000-000000000002 85939755-ba10-4d9d-a4cc-efb7a8e943c4 + // d392b075-4ba1-40ae-8789-af868d56f6ce aecd2294-4b94-434b-8667-4499bb2c9327 + /** + * Recipe data that, when sent to the client, enables book cloning + */ + public static final CraftingData BOOK_CLONING_RECIPE_DATA = CraftingData.fromMulti(UUID.fromString("d1ca6b84-338e-4f2f-9c6b-76cc8b4bd98d")); + /** + * Recipe data that, when sent to the client, enables tool repairing in a crafting table + */ + public static final CraftingData TOOL_REPAIRING_RECIPE_DATA = CraftingData.fromMulti(UUID.fromString("00000000-0000-0000-0000-000000000001")); static { // Get all recipes that are not directly sent from a Java server @@ -88,6 +110,12 @@ public class RecipeRegistry { for (JsonNode entry : items.get("shulker_boxes")) { SHULKER_BOX_DYEING_RECIPES.add(getCraftingDataFromJsonNode(entry)); } + for (JsonNode entry : items.get("suspicious_stew")) { + SUSPICIOUS_STEW_RECIPES.add(getCraftingDataFromJsonNode(entry)); + } + for (JsonNode entry : items.get("tipped_arrows")) { + TIPPED_ARROW_RECIPES.add(getCraftingDataFromJsonNode(entry)); + } } /** @@ -97,11 +125,41 @@ public class RecipeRegistry { */ private static CraftingData getCraftingDataFromJsonNode(JsonNode node) { ItemData output = ItemRegistry.getBedrockItemFromJson(node.get("output").get(0)); + UUID uuid = UUID.randomUUID(); + if (node.get("type").asInt() == 1) { + // Shaped recipe + List shape = new ArrayList<>(); + // Get the shape of the recipe + for (JsonNode chars : node.get("shape")) { + shape.add(chars.asText()); + } + + // In recipes.json each recipe is mapped by a letter + Map letterToRecipe = new HashMap<>(); + Iterator> iterator = node.get("input").fields(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + letterToRecipe.put(entry.getKey(), ItemRegistry.getBedrockItemFromJson(entry.getValue())); + } + + ItemData[] inputs = new ItemData[shape.size() * shape.get(0).length()]; + int i = 0; + // Create a linear array of items from the "cube" of the shape + for (int j = 0; i < shape.size() * shape.get(0).length(); j++) { + for (char c : shape.get(j).toCharArray()) { + ItemData data = letterToRecipe.getOrDefault(String.valueOf(c), ItemData.AIR); + inputs[i] = data; + i++; + } + } + + return CraftingData.fromShaped(uuid.toString(), shape.get(0).length(), shape.size(), + inputs, new ItemData[]{output}, uuid, "crafting_table", 0); + } List inputs = new ObjectArrayList<>(); for (JsonNode entry : node.get("input")) { inputs.add(ItemRegistry.getBedrockItemFromJson(entry)); } - UUID uuid = UUID.randomUUID(); if (node.get("type").asInt() == 5) { // Shulker box return CraftingData.fromShulkerBox(uuid.toString(), diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java index 9ffb4f0d..70303baa 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java @@ -80,8 +80,19 @@ public class JavaDeclareRecipesTranslator extends PacketTranslator Date: Sat, 31 Oct 2020 22:38:56 -0400 Subject: [PATCH 093/205] MapItemTranslator: support map tag being of short value (#1475) GSigns sends this as a short. Who knows why. --- .../translators/nbt/MapItemTranslator.java | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/MapItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/MapItemTranslator.java index c9b49efd..d325af48 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/MapItemTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/MapItemTranslator.java @@ -25,10 +25,7 @@ package org.geysermc.connector.network.translators.item.translators.nbt; -import com.github.steveice10.opennbt.tag.builtin.ByteTag; -import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import com.github.steveice10.opennbt.tag.builtin.IntTag; -import com.github.steveice10.opennbt.tag.builtin.LongTag; +import com.github.steveice10.opennbt.tag.builtin.*; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.ItemRemapper; import org.geysermc.connector.network.translators.item.ItemEntry; @@ -39,14 +36,22 @@ public class MapItemTranslator extends NbtItemStackTranslator { @Override public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) { - IntTag mapId = itemTag.get("map"); + // Can be either an IntTag or ShortTag + Tag mapId = itemTag.get("map"); + if (mapId == null) return; - if (mapId != null) { - itemTag.put(new LongTag("map_uuid", mapId.getValue())); - itemTag.put(new IntTag("map_name_index", mapId.getValue())); - itemTag.put(new ByteTag("map_display_players", (byte) 1)); - itemTag.remove("map"); + int mapValue; + if (mapId.getValue() instanceof Short) { + // Convert to int if necessary + mapValue = (int) (short) mapId.getValue(); + } else { + mapValue = (int) mapId.getValue(); } + + itemTag.put(new LongTag("map_uuid", mapValue)); + itemTag.put(new IntTag("map_name_index", mapValue)); + itemTag.put(new ByteTag("map_display_players", (byte) 1)); + itemTag.remove("map"); } @Override From 3e0d898b6a53e655bffd3b72e779054b04b4b765 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Mon, 2 Nov 2020 14:28:31 -0500 Subject: [PATCH 094/205] Update to 1.16.4 (#1487) * 1.16.4-pre1 support * Update to support ViaVersion 3.2.0 (#1358) * Update to support ViaVersion 3.2.0 This commit updates ViaVersion integration to support their recent mappings changes. Co-authored-by: Nassim * Send adventure settings on gamemode change after gamemode swap * Update Velocity to 1.1.0 proper * Update to 1.16.4 Co-authored-by: Nassim --- bootstrap/spigot/pom.xml | 2 +- .../platform/spigot/GeyserSpigotPlugin.java | 8 ++++ .../world/GeyserSpigotWorldManager.java | 38 ++++++++++++++----- bootstrap/velocity/pom.xml | 2 +- .../velocity/GeyserVelocityPlugin.java | 2 +- connector/pom.xml | 4 +- .../world/JavaNotifyClientTranslator.java | 4 +- 7 files changed, 43 insertions(+), 17 deletions(-) diff --git a/bootstrap/spigot/pom.xml b/bootstrap/spigot/pom.xml index d4dc3326..067b446a 100644 --- a/bootstrap/spigot/pom.xml +++ b/bootstrap/spigot/pom.xml @@ -26,7 +26,7 @@ us.myles viaversion - 3.1.1 + 3.2.0 provided diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java index 892f8feb..59a8db06 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java @@ -43,6 +43,7 @@ import org.geysermc.platform.spigot.command.GeyserSpigotCommandManager; import org.geysermc.platform.spigot.command.SpigotCommandSender; import org.geysermc.platform.spigot.world.GeyserSpigotBlockPlaceListener; import org.geysermc.platform.spigot.world.GeyserSpigotWorldManager; +import us.myles.ViaVersion.api.Via; import java.io.File; import java.io.IOException; @@ -121,6 +122,13 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { this.geyserCommandManager = new GeyserSpigotCommandManager(this, connector); boolean isViaVersion = (Bukkit.getPluginManager().getPlugin("ViaVersion") != null); + if (isViaVersion) { + if (!isCompatible(Via.getAPI().getVersion().replace("-SNAPSHOT", ""), "3.2.0")) { + geyserLogger.warning(LanguageUtils.getLocaleStringLog("geyser.bootstrap.viaversion.too_old", + "https://ci.viaversion.com/job/ViaVersion/")); + isViaVersion = false; + } + } // Used to determine if Block.getBlockData() is present. boolean isLegacy = !isCompatible(Bukkit.getServer().getVersion(), "1.13.0"); if (isLegacy) diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java index 8a92526f..ad3d1cf1 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java @@ -26,6 +26,7 @@ package org.geysermc.platform.spigot.world; import com.fasterxml.jackson.databind.JsonNode; +import com.github.steveice10.mc.protocol.MinecraftConstants; import com.github.steveice10.mc.protocol.data.game.chunk.Chunk; import it.unimi.dsi.fastutil.ints.Int2IntMap; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; @@ -41,14 +42,29 @@ import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.utils.FileUtils; import org.geysermc.connector.utils.GameRule; import org.geysermc.connector.utils.LanguageUtils; -import us.myles.ViaVersion.protocols.protocol1_13_1to1_13.Protocol1_13_1To1_13; -import us.myles.ViaVersion.protocols.protocol1_16_2to1_16_1.data.MappingData; +import us.myles.ViaVersion.api.Pair; +import us.myles.ViaVersion.api.protocol.Protocol; +import us.myles.ViaVersion.api.protocol.ProtocolRegistry; +import us.myles.ViaVersion.api.protocol.ProtocolVersion; +import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.Protocol1_13To1_12_2; import java.io.InputStream; +import java.util.List; public class GeyserSpigotWorldManager extends GeyserWorldManager { + /** + * The current client protocol version for ViaVersion usage. + */ + private static final int CLIENT_PROTOCOL_VERSION = MinecraftConstants.PROTOCOL_VERSION; + + /** + * Whether the server is pre-1.13. + */ private final boolean isLegacy; + /** + * Whether the server is pre-1.16 and therefore does not support 3D biomes on an API level guaranteed. + */ private final boolean use3dBiomes; /** * You need ViaVersion to connect to an older server with Geyser. @@ -122,14 +138,16 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { if (isViaVersion) { Block block = world.getBlockAt(x, y, z); // Black magic that gets the old block state ID - int oldBlockId = (block.getType().getId() << 4) | (block.getData() & 0xF); - // Convert block state from old version -> 1.13 -> 1.13.1 -> 1.14 -> 1.15 -> 1.16 -> 1.16.2 - int thirteenBlockId = us.myles.ViaVersion.protocols.protocol1_13to1_12_2.data.MappingData.blockMappings.getNewId(oldBlockId); - int thirteenPointOneBlockId = Protocol1_13_1To1_13.getNewBlockStateId(thirteenBlockId); - int fourteenBlockId = us.myles.ViaVersion.protocols.protocol1_14to1_13_2.data.MappingData.blockStateMappings.getNewId(thirteenPointOneBlockId); - int fifteenBlockId = us.myles.ViaVersion.protocols.protocol1_15to1_14_4.data.MappingData.blockStateMappings.getNewId(fourteenBlockId); - int sixteenBlockId = us.myles.ViaVersion.protocols.protocol1_16to1_15_2.data.MappingData.blockStateMappings.getNewId(fifteenBlockId); - return MappingData.blockStateMappings.getNewId(sixteenBlockId); + int blockId = (block.getType().getId() << 4) | (block.getData() & 0xF); + // Convert block state from old version (1.12.2) -> 1.13 -> 1.13.1 -> 1.14 -> 1.15 -> 1.16 -> 1.16.2 + blockId = ProtocolRegistry.getProtocol(Protocol1_13To1_12_2.class).getMappingData().getNewBlockId(blockId); + List> protocolList = ProtocolRegistry.getProtocolPath(CLIENT_PROTOCOL_VERSION, + ProtocolVersion.v1_13.getId()); + for (int i = protocolList.size() - 1; i >= 0; i--) { + if (protocolList.get(i).getValue().getMappingData() == null) continue; + blockId = protocolList.get(i).getValue().getMappingData().getNewBlockStateId(blockId); + } + return blockId; } else { return BlockTranslator.AIR; } diff --git a/bootstrap/velocity/pom.xml b/bootstrap/velocity/pom.xml index ee445b6e..babb9a3e 100644 --- a/bootstrap/velocity/pom.xml +++ b/bootstrap/velocity/pom.xml @@ -20,7 +20,7 @@ com.velocitypowered velocity-api - 1.0.0-SNAPSHOT + 1.1.0 provided diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java index 291d7a00..f75c683a 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java @@ -121,7 +121,7 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { this.connector = GeyserConnector.start(PlatformType.VELOCITY, this); this.geyserCommandManager = new GeyserVelocityCommandManager(connector); - this.commandManager.register(new GeyserVelocityCommandExecutor(connector), "geyser"); + this.commandManager.register("geyser", new GeyserVelocityCommandExecutor(connector)); if (geyserConfig.isLegacyPingPassthrough()) { this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(connector); } else { diff --git a/connector/pom.xml b/connector/pom.xml index 19731987..edf14abb 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -109,9 +109,9 @@ compile - com.github.steveice10 + com.github.GeyserMC mcprotocollib - 1b01b1ffef + e4181064d1 compile diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java index 1e102a60..26ecb1e1 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java @@ -103,13 +103,13 @@ public class JavaNotifyClientTranslator extends PacketTranslator Date: Mon, 2 Nov 2020 16:04:08 -0500 Subject: [PATCH 095/205] Send position update every 3 seconds if idle (#1421) * Send position update every 3 seconds if idle Prevents timeouts in certain instances when AFK. * Cancel position sending on dimension switching * Remove debug lines * Create function to centralize movement translation --- .../network/session/GeyserSession.java | 6 ++ .../player/BedrockMovePlayerTranslator.java | 56 +++++++++++++++---- .../connector/utils/DimensionUtils.java | 4 ++ 3 files changed, 55 insertions(+), 11 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index ac72d219..6a2913c8 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -221,6 +221,12 @@ public class GeyserSession implements CommandSender { @Setter private ScheduledFuture bucketScheduledFuture; + /** + * Sends a movement packet every three seconds if the player hasn't moved. Prevents timeouts when AFK in certain instances. + */ + @Setter + private ScheduledFuture movementSendIfIdle; + private boolean reducedDebugInfo = false; @Setter diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockMovePlayerTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockMovePlayerTranslator.java index 8809941b..c5988bf0 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockMovePlayerTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockMovePlayerTranslator.java @@ -25,7 +25,13 @@ package org.geysermc.connector.network.translators.bedrock.entity.player; +import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPositionPacket; +import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPositionRotationPacket; import com.nukkitx.math.vector.Vector3d; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket; +import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket; +import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket; import org.geysermc.connector.common.ChatColor; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.PlayerEntity; @@ -34,11 +40,7 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; -import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPositionRotationPacket; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket; -import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket; -import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket; +import java.util.concurrent.TimeUnit; @Translator(packet = MovePlayerPacket.class) public class BedrockMovePlayerTranslator extends PacketTranslator { @@ -59,13 +61,11 @@ public class BedrockMovePlayerTranslator extends PacketTranslator sendPositionIfIdle(session), + 3, TimeUnit.SECONDS)); + } + + /** + * Adjust the Bedrock position before sending to the Java server to account for inaccuracies in movement between + * the two versions. + * + * @param position the current Bedrock position of the client + * @param onGround whether the Bedrock player is on the ground + * @return the position to send to the Java server. + */ + private Vector3d adjustBedrockPosition(Vector3f position, boolean onGround) { + // We need to parse the float as a string since casting a float to a double causes us to + // lose precision and thus, causes players to get stuck when walking near walls + double javaY = position.getY() - EntityType.PLAYER.getOffset(); + if (onGround) javaY = Math.ceil(javaY * 2) / 2; + + return Vector3d.from(Double.parseDouble(Float.toString(position.getX())), javaY, + Double.parseDouble(Float.toString(position.getZ()))); } public boolean isValidMove(GeyserSession session, MovePlayerPacket.Mode mode, Vector3f currentPosition, Vector3f newPosition) { @@ -147,4 +169,16 @@ public class BedrockMovePlayerTranslator extends PacketTranslator sendPositionIfIdle(session), + 3, TimeUnit.SECONDS)); + } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/DimensionUtils.java b/connector/src/main/java/org/geysermc/connector/utils/DimensionUtils.java index 7b283e9c..30d69258 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/DimensionUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/DimensionUtils.java @@ -52,6 +52,10 @@ public class DimensionUtils { if (javaDimension.equals(player.getDimension())) return; + if (session.getMovementSendIfIdle() != null) { + session.getMovementSendIfIdle().cancel(true); + } + session.getEntityCache().removeAllEntities(); session.getItemFrameCache().clear(); if (session.getPendingDimSwitches().getAndIncrement() > 0) { From ea521078e11b9d158fa28427d940fb191dc3d671 Mon Sep 17 00:00:00 2001 From: Matthias Neid Date: Mon, 2 Nov 2020 22:44:45 +0100 Subject: [PATCH 096/205] Update supported version in README to 1.16.4 (#1488) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f7201bb0..e2efc5b4 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have now joined us here! -### Currently supporting Minecraft Bedrock v1.16.x and Minecraft Java v1.16.3. +### Currently supporting Minecraft Bedrock v1.16.x and Minecraft Java v1.16.4. ## Setting Up Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set up Geyser. From ce64dd27887789cff36b0e9c8bf2be160993f57d Mon Sep 17 00:00:00 2001 From: David Choo Date: Wed, 4 Nov 2020 20:30:55 -0500 Subject: [PATCH 097/205] Fix sitting with armor stand and animal mount offsets (#1492) * Fix sitting with armor stand and animal offsets * Fix riding players * Moved @Getter --- .../entity/living/ArmorStandEntity.java | 4 ++- .../JavaEntitySetPassengersTranslator.java | 34 ++++++++++++------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/ArmorStandEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/ArmorStandEntity.java index 07496093..b61aeda9 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/ArmorStandEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/ArmorStandEntity.java @@ -29,6 +29,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadat import com.github.steveice10.mc.protocol.data.game.entity.metadata.MetadataType; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import lombok.Getter; import org.geysermc.connector.entity.LivingEntity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; @@ -36,6 +37,7 @@ import org.geysermc.connector.network.session.GeyserSession; public class ArmorStandEntity extends LivingEntity { // These are used to store the state of the armour stand for use when handling invisibility + @Getter private boolean isMarker = false; private boolean isInvisible = false; private boolean isSmall = false; @@ -47,7 +49,7 @@ public class ArmorStandEntity extends LivingEntity { @Override public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) { // Fake the height to be above where it is so the nametag appears in the right location for invisible non-marker armour stands - if (!isMarker && isInvisible) { + if (!isMarker && isInvisible && passengers.isEmpty()) { position = position.add(0d, entityType.getHeight() * (isSmall ? 0.55d : 1d), 0d); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntitySetPassengersTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntitySetPassengersTranslator.java index 64f0e3e9..0fecf118 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntitySetPassengersTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntitySetPassengersTranslator.java @@ -33,6 +33,8 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityLinkData; import com.nukkitx.protocol.bedrock.packet.SetEntityLinkPacket; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import org.geysermc.connector.entity.Entity; +import org.geysermc.connector.entity.living.ArmorStandEntity; +import org.geysermc.connector.entity.living.animal.AnimalEntity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; @@ -46,6 +48,10 @@ public class JavaEntitySetPassengersTranslator extends PacketTranslator Date: Thu, 5 Nov 2020 14:59:01 -0500 Subject: [PATCH 098/205] Fix bell sound and visuals (#1502) * Fix bell sound and visuals The bell sound now correctly plays. Bells will also visually ring when rung by another player. * Compress elses * Add more whitespace to new code --- .../java/world/JavaBlockValueTranslator.java | 51 +++++++++++++++---- connector/src/main/resources/languages | 2 +- connector/src/main/resources/mappings | 2 +- 3 files changed, 42 insertions(+), 13 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockValueTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockValueTranslator.java index 3d3df51c..903cad0c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockValueTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockValueTranslator.java @@ -25,6 +25,7 @@ package org.geysermc.connector.network.translators.java.world; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.github.steveice10.mc.protocol.data.game.world.block.value.*; import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerBlockValuePacket; import com.nukkitx.math.vector.Vector3i; @@ -53,16 +54,12 @@ public class JavaBlockValueTranslator extends PacketTranslator 0 ? 1 : 0); session.sendUpstreamPacket(blockEventPacket); - } - if (packet.getValue() instanceof EndGatewayValue) { + } else if (packet.getValue() instanceof EndGatewayValue) { blockEventPacket.setEventType(1); session.sendUpstreamPacket(blockEventPacket); - } - if (packet.getValue() instanceof NoteBlockValue) { + } else if (packet.getValue() instanceof NoteBlockValue) { NoteblockBlockEntityTranslator.translate(session, packet.getPosition()); - return; - } - if (packet.getValue() instanceof PistonValue) { + } else if (packet.getValue() instanceof PistonValue) { PistonValueType type = (PistonValueType) packet.getType(); // Unlike everything else, pistons need a block entity packet to convey motion @@ -73,14 +70,46 @@ public class JavaBlockValueTranslator extends PacketTranslator Date: Thu, 5 Nov 2020 22:36:22 +0100 Subject: [PATCH 099/205] Block entity performance improvements (#1481) * BlockEntity performance improvements * Use chunk cache if possible for block caching * Get new block state from ViaVersion if block entity * Add Javadoc for FlowerPotBlockEntityTranslator.isFlowerBlock * Remove debug line * Don't add all RequiresBlockState instances if cache chunks is enabled * Double chest map get optimization * Last changes Co-authored-by: DoctorMacc --- .../world/GeyserSpigotWorldManager.java | 58 ++++++---- .../connector/entity/ItemFrameEntity.java | 13 +-- ...BedrockInventoryTransactionTranslator.java | 7 +- .../translators/item/ItemRegistry.java | 10 +- .../translators/item/ItemTranslator.java | 7 +- .../item/translators/BannerTranslator.java | 108 +++++++++--------- .../java/world/JavaChunkDataTranslator.java | 7 +- .../world/JavaUpdateTileEntityTranslator.java | 25 ++-- .../world/block/BlockStateValues.java | 41 +++---- .../entity/BannerBlockEntityTranslator.java | 18 +-- .../entity/BedBlockEntityTranslator.java | 16 +-- .../block/entity/BedrockOnlyBlockEntity.java | 3 +- .../block/entity/BlockEntityTranslator.java | 30 +++-- .../entity/CampfireBlockEntityTranslator.java | 10 +- .../CommandBlockBlockEntityTranslator.java | 33 +++--- .../DoubleChestBlockEntityTranslator.java | 59 ++++------ .../entity/EmptyBlockEntityTranslator.java | 9 +- .../EndGatewayBlockEntityTranslator.java | 15 +-- .../FlowerPotBlockEntityTranslator.java | 46 +++++--- .../JigsawBlockBlockEntityTranslator.java | 19 ++- .../NoteblockBlockEntityTranslator.java | 7 +- .../entity/PistonBlockEntityTranslator.java | 20 ++-- .../ShulkerBoxBlockEntityTranslator.java | 17 +-- .../entity/SignBlockEntityTranslator.java | 73 ++++++------ .../entity/SkullBlockEntityTranslator.java | 19 ++- .../entity/SpawnerBlockEntityTranslator.java | 57 +++++---- .../connector/utils/BlockEntityUtils.java | 15 ++- .../geysermc/connector/utils/ChunkUtils.java | 24 ++-- 28 files changed, 362 insertions(+), 404 deletions(-) diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java index ad3d1cf1..28b2da3a 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java @@ -43,16 +43,19 @@ import org.geysermc.connector.utils.FileUtils; import org.geysermc.connector.utils.GameRule; import org.geysermc.connector.utils.LanguageUtils; import us.myles.ViaVersion.api.Pair; +import us.myles.ViaVersion.api.Via; +import us.myles.ViaVersion.api.data.MappingData; +import us.myles.ViaVersion.api.minecraft.Position; import us.myles.ViaVersion.api.protocol.Protocol; import us.myles.ViaVersion.api.protocol.ProtocolRegistry; import us.myles.ViaVersion.api.protocol.ProtocolVersion; import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.Protocol1_13To1_12_2; +import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.storage.BlockStorage; import java.io.InputStream; import java.util.List; public class GeyserSpigotWorldManager extends GeyserWorldManager { - /** * The current client protocol version for ViaVersion usage. */ @@ -99,8 +102,9 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { } // Only load in the biomes that are present in this version of Minecraft for (Biome enumBiome : Biome.values()) { - if (biomes.has(enumBiome.toString())) { - biomeToIdMap.put(enumBiome.ordinal(), biomes.get(enumBiome.toString()).intValue()); + JsonNode biome = biomes.get(enumBiome.toString()); + if (biome != null) { + biomeToIdMap.put(enumBiome.ordinal(), biome.intValue()); } else { GeyserConnector.getInstance().getLogger().debug("No biome mapping found for " + enumBiome.toString() + ", defaulting to 0"); @@ -127,30 +131,38 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { public static int getLegacyBlock(GeyserSession session, int x, int y, int z, boolean isViaVersion) { if (isViaVersion) { - return getLegacyBlock(Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld(), x, y, z, true); + Player bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername()); + // Get block entity storage + BlockStorage storage = Via.getManager().getConnection(bukkitPlayer.getUniqueId()).get(BlockStorage.class); + return getLegacyBlock(storage, bukkitPlayer.getWorld(), x, y, z); } else { return BlockTranslator.AIR; } } @SuppressWarnings("deprecation") - public static int getLegacyBlock(World world, int x, int y, int z, boolean isViaVersion) { - if (isViaVersion) { - Block block = world.getBlockAt(x, y, z); - // Black magic that gets the old block state ID - int blockId = (block.getType().getId() << 4) | (block.getData() & 0xF); - // Convert block state from old version (1.12.2) -> 1.13 -> 1.13.1 -> 1.14 -> 1.15 -> 1.16 -> 1.16.2 - blockId = ProtocolRegistry.getProtocol(Protocol1_13To1_12_2.class).getMappingData().getNewBlockId(blockId); - List> protocolList = ProtocolRegistry.getProtocolPath(CLIENT_PROTOCOL_VERSION, - ProtocolVersion.v1_13.getId()); - for (int i = protocolList.size() - 1; i >= 0; i--) { - if (protocolList.get(i).getValue().getMappingData() == null) continue; - blockId = protocolList.get(i).getValue().getMappingData().getNewBlockStateId(blockId); + public static int getLegacyBlock(BlockStorage storage, World world, int x, int y, int z) { + Block block = world.getBlockAt(x, y, z); + // Black magic that gets the old block state ID + int blockId = (block.getType().getId() << 4) | (block.getData() & 0xF); + // Convert block state from old version (1.12.2) -> 1.13 -> 1.13.1 -> 1.14 -> 1.15 -> 1.16 -> 1.16.2 + blockId = ProtocolRegistry.getProtocol(Protocol1_13To1_12_2.class).getMappingData().getNewBlockId(blockId); + List> protocolList = ProtocolRegistry.getProtocolPath(CLIENT_PROTOCOL_VERSION, + ProtocolVersion.v1_13.getId()); + // Translate block entity differences - some information was stored in block tags and not block states + if (storage.isWelcome(blockId)) { // No getOrDefault method + BlockStorage.ReplacementData data = storage.get(new Position(x, (short) y, z)); + if (data != null && data.getReplacement() != -1) { + blockId = data.getReplacement(); } - return blockId; - } else { - return BlockTranslator.AIR; } + for (int i = protocolList.size() - 1; i >= 0; i--) { + MappingData mappingData = protocolList.get(i).getValue().getMappingData(); + if (mappingData != null) { + blockId = mappingData.getNewBlockStateId(blockId); + } + } + return blockId; } @Override @@ -162,11 +174,13 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { return; } World world = bukkitPlayer.getWorld(); - if (this.isLegacy) { + if (this.isLegacy) { + // Get block entity storage + BlockStorage storage = Via.getManager().getConnection(bukkitPlayer.getUniqueId()).get(BlockStorage.class); for (int blockY = 0; blockY < 16; blockY++) { // Cache-friendly iteration order for (int blockZ = 0; blockZ < 16; blockZ++) { for (int blockX = 0; blockX < 16; blockX++) { - chunk.set(blockX, blockY, blockZ, getLegacyBlock(world, (x << 4) + blockX, (y << 4) + blockY, (z << 4) + blockZ, true)); + chunk.set(blockX, blockY, blockZ, getLegacyBlock(storage, world, (x << 4) + blockX, (y << 4) + blockY, (z << 4) + blockZ)); } } } @@ -176,7 +190,7 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { for (int blockZ = 0; blockZ < 16; blockZ++) { for (int blockX = 0; blockX < 16; blockX++) { Block block = world.getBlockAt((x << 4) + blockX, (y << 4) + blockY, (z << 4) + blockZ); - int id = BlockTranslator.getJavaIdBlockMap().getOrDefault(block.getBlockData().getAsString(), 0); + int id = BlockTranslator.getJavaIdBlockMap().getOrDefault(block.getBlockData().getAsString(), BlockTranslator.AIR); chunk.set(blockX, blockY, blockZ, id); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java b/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java index f9d2ace4..501c7e46 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java @@ -141,6 +141,7 @@ public class ItemFrameEntity extends Entity { UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket(); updateBlockPacket.setDataLayer(0); updateBlockPacket.setBlockPosition(bedrockPosition); + // TODO 1.16.100 set to BEDROCK_AIR updateBlockPacket.setRuntimeId(0); updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.PRIORITY); updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NETWORK); @@ -196,18 +197,6 @@ public class ItemFrameEntity extends Entity { return session.getItemFrameCache().getOrDefault(position, -1); } - /** - * Determines if the position contains an item frame. - * Does largely the same thing as getItemFrameEntityId, but for speed purposes is implemented separately, - * since every block destroy packet has to check for an item frame. - * @param position position of block. - * @param session GeyserSession. - * @return true if position contains item frame, false if not. - */ - public static boolean positionContainsItemFrame(GeyserSession session, Vector3i position) { - return session.getItemFrameCache().containsKey(position); - } - /** * Force-remove from the position-to-ID map so it doesn't cause conflicts. * @param session GeyserSession. diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java index b2a70146..81a2cd50 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java @@ -194,10 +194,9 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator ITEM_ENTRIES.values() - .stream().filter(itemEntry -> itemEntry.getJavaIdentifier().equals(key)).findFirst().orElse(null)); + return JAVA_IDENTIFIER_MAP.computeIfAbsent(javaIdentifier, key -> { + for (ItemEntry entry : ITEM_ENTRIES.values()) { + if (entry.getJavaIdentifier().equals(key)) { + return entry; + } + } + return null; + }); } /** diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java index f95a0ccc..55db9a25 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java @@ -51,7 +51,6 @@ import java.util.*; import java.util.stream.Collectors; public abstract class ItemTranslator { - private static final Int2ObjectMap ITEM_STACK_TRANSLATORS = new Int2ObjectOpenHashMap<>(); private static final List NBT_TRANSLATORS; @@ -220,7 +219,7 @@ public abstract class ItemTranslator { public abstract List getAppliedItems(); public NbtMap translateNbtToBedrock(com.github.steveice10.opennbt.tag.builtin.CompoundTag tag) { - Map javaValue = new HashMap<>(); + NbtMapBuilder builder = NbtMap.builder(); if (tag.getValue() != null && !tag.getValue().isEmpty()) { for (String str : tag.getValue().keySet()) { com.github.steveice10.opennbt.tag.builtin.Tag javaTag = tag.get(str); @@ -228,11 +227,9 @@ public abstract class ItemTranslator { if (translatedTag == null) continue; - javaValue.put(javaTag.getName(), translatedTag); + builder.put(javaTag.getName(), translatedTag); } } - NbtMapBuilder builder = NbtMap.builder(); - javaValue.forEach(builder::put); return builder.build(); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java index f4bfdfb6..200271cf 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java @@ -26,20 +26,16 @@ package org.geysermc.connector.network.translators.item.translators; import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; -import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import com.github.steveice10.opennbt.tag.builtin.IntTag; -import com.github.steveice10.opennbt.tag.builtin.ListTag; -import com.github.steveice10.opennbt.tag.builtin.StringTag; -import com.github.steveice10.opennbt.tag.builtin.Tag; +import com.github.steveice10.opennbt.tag.builtin.*; import com.nukkitx.nbt.NbtList; import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.nbt.NbtType; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import org.geysermc.connector.network.translators.ItemRemapper; +import org.geysermc.connector.network.translators.item.ItemEntry; import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.network.translators.item.ItemTranslator; -import org.geysermc.connector.network.translators.item.ItemEntry; import java.util.ArrayList; import java.util.HashMap; @@ -49,54 +45,13 @@ import java.util.stream.Collectors; @ItemRemapper public class BannerTranslator extends ItemTranslator { - private final List appliedItems; public BannerTranslator() { - appliedItems = ItemRegistry.ITEM_ENTRIES.values().stream().filter(entry -> entry.getJavaIdentifier().endsWith("banner")).collect(Collectors.toList()); - } - - @Override - public ItemData translateToBedrock(ItemStack itemStack, ItemEntry itemEntry) { - if (itemStack.getNbt() == null) return super.translateToBedrock(itemStack, itemEntry); - - ItemData itemData = super.translateToBedrock(itemStack, itemEntry); - - CompoundTag blockEntityTag = itemStack.getNbt().get("BlockEntityTag"); - if (blockEntityTag.contains("Patterns")) { - ListTag patterns = blockEntityTag.get("Patterns"); - - NbtMapBuilder builder = itemData.getTag().toBuilder(); - builder.put("Patterns", convertBannerPattern(patterns)); - - itemData = ItemData.of(itemData.getId(), itemData.getDamage(), itemData.getCount(), builder.build()); - } - - return itemData; - } - - @Override - public ItemStack translateToJava(ItemData itemData, ItemEntry itemEntry) { - if (itemData.getTag() == null) return super.translateToJava(itemData, itemEntry); - - ItemStack itemStack = super.translateToJava(itemData, itemEntry); - - NbtMap nbtTag = itemData.getTag(); - if (nbtTag.containsKey("Patterns", NbtType.COMPOUND)) { - List patterns = nbtTag.getList("Patterns", NbtType.COMPOUND); - - CompoundTag blockEntityTag = new CompoundTag("BlockEntityTag"); - blockEntityTag.put(convertBannerPattern(patterns)); - - itemStack.getNbt().put(blockEntityTag); - } - - return itemStack; - } - - @Override - public List getAppliedItems() { - return appliedItems; + appliedItems = ItemRegistry.ITEM_ENTRIES.values() + .stream() + .filter(entry -> entry.getJavaIdentifier().endsWith("banner")) + .collect(Collectors.toList()); } /** @@ -133,7 +88,6 @@ public class BannerTranslator extends ItemTranslator { return NbtMap.builder() .putInt("Color", 15 - (int) pattern.get("Color").getValue()) - .putString("Pattern", (String) pattern.get("Pattern").getValue()) .putString("Pattern", patternName) .build(); } @@ -147,8 +101,7 @@ public class BannerTranslator extends ItemTranslator { public static ListTag convertBannerPattern(List patterns) { List tagsList = new ArrayList<>(); for (Object patternTag : patterns) { - CompoundTag newPatternTag = getJavaBannerPattern((NbtMap) patternTag); - tagsList.add(newPatternTag); + tagsList.add(getJavaBannerPattern((NbtMap) patternTag)); } return new ListTag("Patterns", tagsList); @@ -167,4 +120,51 @@ public class BannerTranslator extends ItemTranslator { return new CompoundTag("", tags); } + + @Override + public ItemData translateToBedrock(ItemStack itemStack, ItemEntry itemEntry) { + if (itemStack.getNbt() == null) { + return super.translateToBedrock(itemStack, itemEntry); + } + + ItemData itemData = super.translateToBedrock(itemStack, itemEntry); + + CompoundTag blockEntityTag = itemStack.getNbt().get("BlockEntityTag"); + if (blockEntityTag.contains("Patterns")) { + ListTag patterns = blockEntityTag.get("Patterns"); + + NbtMapBuilder builder = itemData.getTag().toBuilder(); + builder.put("Patterns", convertBannerPattern(patterns)); + + itemData = ItemData.of(itemData.getId(), itemData.getDamage(), itemData.getCount(), builder.build()); + } + + return itemData; + } + + @Override + public ItemStack translateToJava(ItemData itemData, ItemEntry itemEntry) { + if (itemData.getTag() == null) { + return super.translateToJava(itemData, itemEntry); + } + + ItemStack itemStack = super.translateToJava(itemData, itemEntry); + + NbtMap nbtTag = itemData.getTag(); + if (nbtTag.containsKey("Patterns", NbtType.COMPOUND)) { + List patterns = nbtTag.getList("Patterns", NbtType.COMPOUND); + + CompoundTag blockEntityTag = new CompoundTag("BlockEntityTag"); + blockEntityTag.put(convertBannerPattern(patterns)); + + itemStack.getNbt().put(blockEntityTag); + } + + return itemStack; + } + + @Override + public List getAppliedItems() { + return appliedItems; + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaChunkDataTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaChunkDataTranslator.java index cd1a321c..741632bc 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaChunkDataTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaChunkDataTranslator.java @@ -45,14 +45,13 @@ import org.geysermc.connector.utils.ChunkUtils; @Translator(packet = ServerChunkDataPacket.class) public class JavaChunkDataTranslator extends PacketTranslator { - /** * Determines if we should process non-full chunks */ - private final boolean isCacheChunks; + private final boolean cacheChunks; public JavaChunkDataTranslator() { - isCacheChunks = GeyserConnector.getInstance().getConfig().isCacheChunks(); + cacheChunks = GeyserConnector.getInstance().getConfig().isCacheChunks(); } @Override @@ -61,7 +60,7 @@ public class JavaChunkDataTranslator extends PacketTranslator { + private final boolean cacheChunks; + + public JavaUpdateTileEntityTranslator() { + cacheChunks = GeyserConnector.getInstance().getConfig().isCacheChunks(); + } @Override public void translate(ServerUpdateTileEntityPacket packet, GeyserSession session) { @@ -48,16 +54,17 @@ public class JavaUpdateTileEntityTranslator extends PacketTranslator= 2 && session.getGameMode() == GameMode.CREATIVE && packet.getNbt().size() > 5) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java index 305118e6..bfd59cc7 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java @@ -36,7 +36,6 @@ import java.util.Map; * Used for block entities if the Java block state contains Bedrock block information. */ public class BlockStateValues { - private static final Int2IntMap BANNER_COLORS = new Int2IntOpenHashMap(); private static final Int2ByteMap BED_COLORS = new Int2ByteOpenHashMap(); private static final Int2ByteMap COMMAND_BLOCK_VALUES = new Int2ByteOpenHashMap(); @@ -52,7 +51,8 @@ public class BlockStateValues { /** * Determines if the block state contains Bedrock block information - * @param entry The String to JsonNode map used in BlockTranslator + * + * @param entry The String to JsonNode map used in BlockTranslator * @param javaBlockState the Java Block State of the block */ public static void storeBlockStateValues(Map.Entry entry, int javaBlockState) { @@ -101,7 +101,7 @@ public class BlockStateValues { } JsonNode skullVariation = entry.getValue().get("variation"); - if(skullVariation != null) { + if (skullVariation != null) { SKULL_VARIANTS.put(javaBlockState, (byte) skullVariation.intValue()); } @@ -124,10 +124,7 @@ public class BlockStateValues { * @return Banner color integer or -1 if no color */ public static int getBannerColor(int state) { - if (BANNER_COLORS.containsKey(state)) { - return BANNER_COLORS.get(state); - } - return -1; + return BANNER_COLORS.getOrDefault(state, -1); } /** @@ -138,10 +135,7 @@ public class BlockStateValues { * @return Bed color byte or -1 if no color */ public static byte getBedColor(int state) { - if (BED_COLORS.containsKey(state)) { - return BED_COLORS.get(state); - } - return -1; + return BED_COLORS.getOrDefault(state, (byte) -1); } /** @@ -157,6 +151,7 @@ public class BlockStateValues { /** * All double chest values are part of the block state in Java and part of the block entity tag in Bedrock. * This gives the DoubleChestValue that can be calculated into the final tag. + * * @return The map of all DoubleChestValues. */ public static Int2ObjectMap getDoubleChestValues() { @@ -165,6 +160,7 @@ public class BlockStateValues { /** * Get the Int2ObjectMap of flower pot block states to containing plant + * * @return Int2ObjectMap of flower pot values */ public static Int2ObjectMap getFlowerPotValues() { @@ -173,6 +169,7 @@ public class BlockStateValues { /** * Get the map of contained flower pot plants to Bedrock CompoundTag + * * @return Map of flower pot blocks. */ public static Map getFlowerPotBlocks() { @@ -182,18 +179,17 @@ public class BlockStateValues { /** * The note that noteblocks output when hit is part of the block state in Java but sent as a BlockEventPacket in Bedrock. * This gives an integer pitch that Bedrock can use. + * * @param state BlockState of the block * @return note block note integer or -1 if not present */ public static int getNoteblockPitch(int state) { - if (NOTEBLOCK_PITCHES.containsKey(state)) { - return NOTEBLOCK_PITCHES.get(state); - } - return -1; + return NOTEBLOCK_PITCHES.getOrDefault(state, -1); } /** * Get the Int2BooleanMap showing if a piston block state is extended or not. + * * @return the Int2BooleanMap of piston extensions. */ public static Int2BooleanMap getPistonValues() { @@ -212,10 +208,7 @@ public class BlockStateValues { * @return Skull variant byte or -1 if no variant */ public static byte getSkullVariant(int state) { - if (SKULL_VARIANTS.containsKey(state)) { - return SKULL_VARIANTS.get(state); - } - return -1; + return SKULL_VARIANTS.getOrDefault(state, (byte) -1); } /** @@ -226,10 +219,7 @@ public class BlockStateValues { * @return Skull rotation value or -1 if no value */ public static byte getSkullRotation(int state) { - if (SKULL_ROTATIONS.containsKey(state)) { - return SKULL_ROTATIONS.get(state); - } - return -1; + return SKULL_ROTATIONS.getOrDefault(state, (byte) -1); } @@ -241,9 +231,6 @@ public class BlockStateValues { * @return Shulker direction value or -1 if no value */ public static byte getShulkerBoxDirection(int state) { - if (SHULKERBOX_DIRECTIONS.containsKey(state)) { - return SHULKERBOX_DIRECTIONS.get(state); - } - return -1; + return SHULKERBOX_DIRECTIONS.getOrDefault(state, (byte) -1); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java index 57393a6c..f5e1d594 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java @@ -27,39 +27,31 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.ListTag; +import com.nukkitx.nbt.NbtMapBuilder; import org.geysermc.connector.network.translators.item.translators.BannerTranslator; import org.geysermc.connector.network.translators.world.block.BlockStateValues; -import java.util.HashMap; -import java.util.Map; - @BlockEntity(name = "Banner", regex = "banner") public class BannerBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { - @Override public boolean isBlock(int blockState) { return BlockStateValues.getBannerColor(blockState) != -1; } @Override - public Map translateTag(CompoundTag tag, int blockState) { - Map tags = new HashMap<>(); - + public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { int bannerColor = BlockStateValues.getBannerColor(blockState); if (bannerColor != -1) { - tags.put("Base", 15 - bannerColor); + builder.put("Base", 15 - bannerColor); } if (tag.contains("Patterns")) { ListTag patterns = tag.get("Patterns"); - tags.put("Patterns", BannerTranslator.convertBannerPattern(patterns)); + builder.put("Patterns", BannerTranslator.convertBannerPattern(patterns)); } if (tag.contains("CustomName")) { - tags.put("CustomName", tag.get("CustomName").getValue()); + builder.put("CustomName", tag.get("CustomName").getValue()); } - - return tags; } - } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedBlockEntityTranslator.java index 080bdc3b..0067cc41 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedBlockEntityTranslator.java @@ -26,27 +26,23 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.nukkitx.nbt.NbtMapBuilder; import org.geysermc.connector.network.translators.world.block.BlockStateValues; -import java.util.HashMap; -import java.util.Map; - @BlockEntity(name = "Bed", regex = "bed") public class BedBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { - @Override public boolean isBlock(int blockState) { return BlockStateValues.getBedColor(blockState) != -1; } @Override - public Map translateTag(CompoundTag tag, int blockState) { - Map tags = new HashMap<>(); + public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { byte bedcolor = BlockStateValues.getBedColor(blockState); // Just in case... - if (bedcolor == -1) bedcolor = 0; - tags.put("color", bedcolor); - return tags; + if (bedcolor == -1) { + bedcolor = 0; + } + builder.put("color", bedcolor); } - } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedrockOnlyBlockEntity.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedrockOnlyBlockEntity.java index d2e4537f..646929f3 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedrockOnlyBlockEntity.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedrockOnlyBlockEntity.java @@ -33,7 +33,6 @@ import org.geysermc.connector.network.session.GeyserSession; * Implemented only if a block is a block entity in Bedrock and not Java Edition. */ public interface BedrockOnlyBlockEntity { - /** * Update the block on Bedrock Edition. * @param session GeyserSession. @@ -49,7 +48,7 @@ public interface BedrockOnlyBlockEntity { * @return Bedrock tag, or null if not a Bedrock-only Block Entity */ static NbtMap getTag(Vector3i position, int blockState) { - if (new FlowerPotBlockEntityTranslator().isBlock(blockState)) { + if (FlowerPotBlockEntityTranslator.isFlowerBlock(blockState)) { return FlowerPotBlockEntityTranslator.getTag(blockState, position); } else if (PistonBlockEntityTranslator.isBlock(blockState)) { return PistonBlockEntityTranslator.getTag(blockState, position); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java index 4df4fd95..67963652 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java @@ -41,10 +41,16 @@ import org.reflections.Reflections; import java.util.HashMap; import java.util.Map; +/** + * The class that all block entities (on both Java and Bedrock) should translate with + */ public abstract class BlockEntityTranslator { - public static final Map BLOCK_ENTITY_TRANSLATORS = new HashMap<>(); - public static ObjectArrayList REQUIRES_BLOCK_STATE_LIST = new ObjectArrayList<>(); + /** + * A list of all block entities that require the Java block state in order to fill out their block entity information. + * This list will be smaller with cache chunks on as we don't need to double-cache data + */ + public static final ObjectArrayList REQUIRES_BLOCK_STATE_LIST = new ObjectArrayList<>(); /** * Contains a list of irregular block entity name translations that can't be fit into the regex @@ -78,27 +84,33 @@ public abstract class BlockEntityTranslator { GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.network.translator.block_entity.failed", clazz.getCanonicalName())); } } + boolean cacheChunks = GeyserConnector.getInstance().getConfig().isCacheChunks(); for (Class clazz : ref.getSubTypesOf(RequiresBlockState.class)) { GeyserConnector.getInstance().getLogger().debug("Found block entity that requires block state: " + clazz.getCanonicalName()); try { - REQUIRES_BLOCK_STATE_LIST.add((RequiresBlockState) clazz.newInstance()); + RequiresBlockState requiresBlockState = (RequiresBlockState) clazz.newInstance(); + if (cacheChunks && !(requiresBlockState instanceof BedrockOnlyBlockEntity)) { + // Not needed to put this one in the map; cache chunks takes care of that for us + GeyserConnector.getInstance().getLogger().debug("Not adding because cache chunks is enabled."); + continue; + } + REQUIRES_BLOCK_STATE_LIST.add(requiresBlockState); } catch (InstantiationException | IllegalAccessException e) { GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.network.translator.block_state.failed", clazz.getCanonicalName())); } } } - public abstract Map translateTag(CompoundTag tag, int blockState); + public abstract void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState); public NbtMap getBlockEntityTag(String id, CompoundTag tag, int blockState) { - int x = Integer.parseInt(String.valueOf(tag.getValue().get("x").getValue())); - int y = Integer.parseInt(String.valueOf(tag.getValue().get("y").getValue())); - int z = Integer.parseInt(String.valueOf(tag.getValue().get("z").getValue())); + int x = ((IntTag) tag.getValue().get("x")).getValue(); + int y = ((IntTag) tag.getValue().get("y")).getValue(); + int z = ((IntTag) tag.getValue().get("z")).getValue(); NbtMapBuilder tagBuilder = getConstantBedrockTag(BlockEntityUtils.getBedrockBlockEntityId(id), x, y, z).toBuilder(); - Map translatedTags = translateTag(tag, blockState); - translatedTags.forEach(tagBuilder::put); + translateTag(tagBuilder, tag, blockState); return tagBuilder.build(); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CampfireBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CampfireBlockEntityTranslator.java index d6ac0281..3e4f9fb9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CampfireBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CampfireBlockEntityTranslator.java @@ -32,22 +32,16 @@ import com.nukkitx.nbt.NbtMapBuilder; import org.geysermc.connector.network.translators.item.ItemEntry; import org.geysermc.connector.network.translators.item.ItemRegistry; -import java.util.HashMap; -import java.util.Map; - @BlockEntity(name = "Campfire", regex = "campfire") public class CampfireBlockEntityTranslator extends BlockEntityTranslator { - @Override - public Map translateTag(CompoundTag tag, int blockState) { - Map tags = new HashMap<>(); + public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { ListTag items = tag.get("Items"); int i = 1; for (com.github.steveice10.opennbt.tag.builtin.Tag itemTag : items.getValue()) { - tags.put("Item" + i, getItem((CompoundTag) itemTag)); + builder.put("Item" + i, getItem((CompoundTag) itemTag)); i++; } - return tags; } protected NbtMap getItem(CompoundTag tag) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CommandBlockBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CommandBlockBlockEntityTranslator.java index 6bc940ad..2484dba7 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CommandBlockBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CommandBlockBlockEntityTranslator.java @@ -26,38 +26,33 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.opennbt.tag.builtin.*; +import com.nukkitx.nbt.NbtMapBuilder; import org.geysermc.connector.network.translators.world.block.BlockStateValues; import org.geysermc.connector.utils.MessageUtils; -import java.util.HashMap; -import java.util.Map; - @BlockEntity(name = "CommandBlock", regex = "command_block") public class CommandBlockBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { - @Override - public Map translateTag(CompoundTag tag, int blockState) { - Map map = new HashMap<>(); + public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { if (tag.size() < 5) { - return map; // These values aren't here + return; // These values aren't here } // Java infers from the block state, but Bedrock needs it in the tag - map.put("conditionalMode", BlockStateValues.getCommandBlockValues().getOrDefault(blockState, (byte) 0)); + builder.put("conditionalMode", BlockStateValues.getCommandBlockValues().getOrDefault(blockState, (byte) 0)); // Java and Bedrock values - map.put("conditionMet", ((ByteTag) tag.get("conditionMet")).getValue()); - map.put("auto", ((ByteTag) tag.get("auto")).getValue()); - map.put("CustomName", MessageUtils.getBedrockMessage(((StringTag) tag.get("CustomName")).getValue())); - map.put("powered", ((ByteTag) tag.get("powered")).getValue()); - map.put("Command", ((StringTag) tag.get("Command")).getValue()); - map.put("SuccessCount", ((IntTag) tag.get("SuccessCount")).getValue()); - map.put("TrackOutput", ((ByteTag) tag.get("TrackOutput")).getValue()); - map.put("UpdateLastExecution", ((ByteTag) tag.get("UpdateLastExecution")).getValue()); + builder.put("conditionMet", ((ByteTag) tag.get("conditionMet")).getValue()); + builder.put("auto", ((ByteTag) tag.get("auto")).getValue()); + builder.put("CustomName", MessageUtils.getBedrockMessage(((StringTag) tag.get("CustomName")).getValue())); + builder.put("powered", ((ByteTag) tag.get("powered")).getValue()); + builder.put("Command", ((StringTag) tag.get("Command")).getValue()); + builder.put("SuccessCount", ((IntTag) tag.get("SuccessCount")).getValue()); + builder.put("TrackOutput", ((ByteTag) tag.get("TrackOutput")).getValue()); + builder.put("UpdateLastExecution", ((ByteTag) tag.get("UpdateLastExecution")).getValue()); if (tag.get("LastExecution") != null) { - map.put("LastExecution", ((LongTag) tag.get("LastExecution")).getValue()); + builder.put("LastExecution", ((LongTag) tag.get("LastExecution")).getValue()); } else { - map.put("LastExecution", (long) 0); + builder.put("LastExecution", (long) 0); } - return map; } @Override diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/DoubleChestBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/DoubleChestBlockEntityTranslator.java index 5b59420e..47bcf489 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/DoubleChestBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/DoubleChestBlockEntityTranslator.java @@ -33,15 +33,11 @@ import org.geysermc.connector.network.translators.world.block.BlockStateValues; import org.geysermc.connector.network.translators.world.block.DoubleChestValue; import org.geysermc.connector.utils.BlockEntityUtils; -import java.util.HashMap; -import java.util.Map; - /** * Chests have more block entity properties in Bedrock, which is solved by implementing the BedrockOnlyBlockEntity */ @BlockEntity(name = "Chest", regex = "chest") public class DoubleChestBlockEntityTranslator extends BlockEntityTranslator implements BedrockOnlyBlockEntity, RequiresBlockState { - @Override public boolean isBlock(int blockState) { return BlockStateValues.getDoubleChestValues().containsKey(blockState); @@ -51,44 +47,39 @@ public class DoubleChestBlockEntityTranslator extends BlockEntityTranslator impl public void updateBlock(GeyserSession session, int blockState, Vector3i position) { CompoundTag javaTag = getConstantJavaTag("chest", position.getX(), position.getY(), position.getZ()); NbtMapBuilder tagBuilder = getConstantBedrockTag(BlockEntityUtils.getBedrockBlockEntityId("chest"), position.getX(), position.getY(), position.getZ()).toBuilder(); - translateTag(javaTag, blockState).forEach(tagBuilder::put); + translateTag(tagBuilder, javaTag, blockState); BlockEntityUtils.updateBlockEntity(session, tagBuilder.build(), position); } @Override - public Map translateTag(CompoundTag tag, int blockState) { - Map tags = new HashMap<>(); - if (BlockStateValues.getDoubleChestValues().containsKey(blockState)) { - DoubleChestValue chestValues = BlockStateValues.getDoubleChestValues().get(blockState); - if (chestValues != null) { - int x = (int) tag.getValue().get("x").getValue(); - int z = (int) tag.getValue().get("z").getValue(); - // Calculate the position of the other chest based on the Java block state - if (chestValues.isFacingEast) { - if (chestValues.isDirectionPositive) { - // East - z = z + (chestValues.isLeft ? 1 : -1); - } else { - // West - z = z + (chestValues.isLeft ? -1 : 1); - } + public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { + DoubleChestValue chestValues = BlockStateValues.getDoubleChestValues().getOrDefault(blockState, null); + if (chestValues != null) { + int x = (int) tag.getValue().get("x").getValue(); + int z = (int) tag.getValue().get("z").getValue(); + // Calculate the position of the other chest based on the Java block state + if (chestValues.isFacingEast) { + if (chestValues.isDirectionPositive) { + // East + z = z + (chestValues.isLeft ? 1 : -1); } else { - if (chestValues.isDirectionPositive) { - // South - x = x + (chestValues.isLeft ? -1 : 1); - } else { - // North - x = x + (chestValues.isLeft ? 1 : -1); - } + // West + z = z + (chestValues.isLeft ? -1 : 1); } - tags.put("pairx", x); - tags.put("pairz", z); - if (!chestValues.isLeft) { - tags.put("pairlead", (byte) 1); + } else { + if (chestValues.isDirectionPositive) { + // South + x = x + (chestValues.isLeft ? -1 : 1); + } else { + // North + x = x + (chestValues.isLeft ? 1 : -1); } } + builder.put("pairx", x); + builder.put("pairz", z); + if (!chestValues.isLeft) { + builder.put("pairlead", (byte) 1); + } } - return tags; } - } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EmptyBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EmptyBlockEntityTranslator.java index e9715bd3..3926b866 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EmptyBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EmptyBlockEntityTranslator.java @@ -26,16 +26,11 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; - -import java.util.HashMap; -import java.util.Map; +import com.nukkitx.nbt.NbtMapBuilder; @BlockEntity(name = "Empty", regex = "") public class EmptyBlockEntityTranslator extends BlockEntityTranslator { - @Override - public Map translateTag(CompoundTag tag, int blockState) { - return new HashMap<>(); + public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { } - } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EndGatewayBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EndGatewayBlockEntityTranslator.java index af94c560..0bf58822 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EndGatewayBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EndGatewayBlockEntityTranslator.java @@ -28,21 +28,18 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.IntTag; import com.nukkitx.nbt.NbtList; +import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.nbt.NbtType; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; -import java.util.HashMap; import java.util.LinkedHashMap; -import java.util.Map; @BlockEntity(name = "EndGateway", regex = "end_gateway") public class EndGatewayBlockEntityTranslator extends BlockEntityTranslator { - @Override - public Map translateTag(CompoundTag tag, int blockState) { - Map tags = new HashMap<>(); - tags.put("Age", (int) ((long) tag.get("Age").getValue())); + public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { + builder.put("Age", (int) ((long) tag.get("Age").getValue())); // Java sometimes does not provide this tag, but Bedrock crashes if it doesn't exist // Linked coordinates IntList tagsList = new IntArrayList(); @@ -50,8 +47,7 @@ public class EndGatewayBlockEntityTranslator extends BlockEntityTranslator { tagsList.add(getExitPortalCoordinate(tag, "X")); tagsList.add(getExitPortalCoordinate(tag, "Y")); tagsList.add(getExitPortalCoordinate(tag, "Z")); - tags.put("ExitPortal", new NbtList<>(NbtType.INT, tagsList)); - return tags; + builder.put("ExitPortal", new NbtList<>(NbtType.INT, tagsList)); } private int getExitPortalCoordinate(CompoundTag tag, String axis) { @@ -60,6 +56,7 @@ public class EndGatewayBlockEntityTranslator extends BlockEntityTranslator { LinkedHashMap compoundTag = (LinkedHashMap) tag.get("ExitPortal").getValue(); IntTag intTag = (IntTag) compoundTag.get(axis); return intTag.getValue(); - } return 0; + } + return 0; } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/FlowerPotBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/FlowerPotBlockEntityTranslator.java index 7bfcc7ee..f64474ae 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/FlowerPotBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/FlowerPotBlockEntityTranslator.java @@ -35,30 +35,19 @@ import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.utils.BlockEntityUtils; public class FlowerPotBlockEntityTranslator implements BedrockOnlyBlockEntity, RequiresBlockState { - - @Override - public boolean isBlock(int blockState) { - return (BlockStateValues.getFlowerPotValues().containsKey(blockState)); - } - - @Override - public void updateBlock(GeyserSession session, int blockState, Vector3i position) { - BlockEntityUtils.updateBlockEntity(session, getTag(blockState, position), position); - UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket(); - updateBlockPacket.setDataLayer(0); - updateBlockPacket.setRuntimeId(BlockTranslator.getBedrockBlockId(blockState)); - updateBlockPacket.setBlockPosition(position); - updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NEIGHBORS); - updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NETWORK); - updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.PRIORITY); - session.sendUpstreamPacket(updateBlockPacket); - BlockEntityUtils.updateBlockEntity(session, getTag(blockState, position), position); + /** + * @param blockState the Java block state of a potential flower pot block + * @return true if the block is a flower pot + */ + public static boolean isFlowerBlock(int blockState) { + return BlockStateValues.getFlowerPotValues().containsKey(blockState); } /** * Get the Nukkit CompoundTag of the flower pot. + * * @param blockState Java block state of flower pot. - * @param position Bedrock position of flower pot. + * @param position Bedrock position of flower pot. * @return Bedrock tag of flower pot. */ public static NbtMap getTag(int blockState, Vector3i position) { @@ -80,4 +69,23 @@ public class FlowerPotBlockEntityTranslator implements BedrockOnlyBlockEntity, R } return tagBuilder.build(); } + + @Override + public boolean isBlock(int blockState) { + return isFlowerBlock(blockState); + } + + @Override + public void updateBlock(GeyserSession session, int blockState, Vector3i position) { + BlockEntityUtils.updateBlockEntity(session, getTag(blockState, position), position); + UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket(); + updateBlockPacket.setDataLayer(0); + updateBlockPacket.setRuntimeId(BlockTranslator.getBedrockBlockId(blockState)); + updateBlockPacket.setBlockPosition(position); + updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NEIGHBORS); + updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NETWORK); + updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.PRIORITY); + session.sendUpstreamPacket(updateBlockPacket); + BlockEntityUtils.updateBlockEntity(session, getTag(blockState, position), position); + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/JigsawBlockBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/JigsawBlockBlockEntityTranslator.java index 43ac1a96..4fcdfe54 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/JigsawBlockBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/JigsawBlockBlockEntityTranslator.java @@ -27,21 +27,16 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.StringTag; - -import java.util.HashMap; -import java.util.Map; +import com.nukkitx.nbt.NbtMapBuilder; @BlockEntity(name = "JigsawBlock", regex = "jigsaw") public class JigsawBlockBlockEntityTranslator extends BlockEntityTranslator { - @Override - public Map translateTag(CompoundTag tag, int blockState) { - Map map = new HashMap<>(); - map.put("joint", ((StringTag) tag.get("joint")).getValue()); - map.put("name", ((StringTag) tag.get("name")).getValue()); - map.put("target_pool", ((StringTag) tag.get("pool")).getValue()); - map.put("final_state", ((StringTag) tag.get("final_state")).getValue()); - map.put("target", ((StringTag) tag.get("target")).getValue()); - return map; + public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { + builder.put("joint", ((StringTag) tag.get("joint")).getValue()); + builder.put("name", ((StringTag) tag.get("name")).getValue()); + builder.put("target_pool", ((StringTag) tag.get("pool")).getValue()); + builder.put("final_state", ((StringTag) tag.get("final_state")).getValue()); + builder.put("target", ((StringTag) tag.get("target")).getValue()); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/NoteblockBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/NoteblockBlockEntityTranslator.java index f3e43009..fce0a056 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/NoteblockBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/NoteblockBlockEntityTranslator.java @@ -36,21 +36,20 @@ import org.geysermc.connector.utils.ChunkUtils; * Does not implement BlockEntityTranslator because it's only a block entity in Bedrock */ public class NoteblockBlockEntityTranslator implements RequiresBlockState { - @Override public boolean isBlock(int blockState) { return BlockStateValues.getNoteblockPitch(blockState) != -1; } public static void translate(GeyserSession session, Position position) { - int blockState = ChunkUtils.CACHED_BLOCK_ENTITIES.getOrDefault(position, 0); + int blockState = session.getConnector().getConfig().isCacheChunks() ? + session.getConnector().getWorldManager().getBlockAt(session, position) : + ChunkUtils.CACHED_BLOCK_ENTITIES.removeInt(position); BlockEventPacket blockEventPacket = new BlockEventPacket(); blockEventPacket.setBlockPosition(Vector3i.from(position.getX(), position.getY(), position.getZ())); blockEventPacket.setEventType(0); blockEventPacket.setEventData(BlockStateValues.getNoteblockPitch(blockState)); session.sendUpstreamPacket(blockEventPacket); - - ChunkUtils.CACHED_BLOCK_ENTITIES.remove(position); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/PistonBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/PistonBlockEntityTranslator.java index 54feacbe..c8a6e868 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/PistonBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/PistonBlockEntityTranslator.java @@ -34,9 +34,9 @@ import org.geysermc.connector.network.translators.world.block.BlockStateValues; * Pistons are a special case where they are only a block entity on Bedrock. */ public class PistonBlockEntityTranslator { - /** * Used in ChunkUtils to determine if the block is a piston. + * * @param blockState Java BlockState of block. * @return if block is a piston or not. */ @@ -46,8 +46,9 @@ public class PistonBlockEntityTranslator { /** * Calculates the Nukkit CompoundTag to send to the client on chunk + * * @param blockState Java block state of block. - * @param position Bedrock position of piston. + * @param position Bedrock position of piston. * @return Bedrock tag of piston. */ public static NbtMap getTag(int blockState, Vector3i position) { @@ -57,14 +58,13 @@ public class PistonBlockEntityTranslator { .putInt("z", position.getZ()) .putByte("isMovable", (byte) 1) .putString("id", "PistonArm"); - if (BlockStateValues.getPistonValues().containsKey(blockState)) { - boolean extended = BlockStateValues.getPistonValues().get(blockState); - // 1f if extended, otherwise 0f - tagBuilder.putFloat("Progress", (extended) ? 1.0f : 0.0f); - // 1 if sticky, 0 if not - tagBuilder.putByte("Sticky", (byte)((BlockStateValues.isStickyPiston(blockState)) ? 1 : 0)); - } + + boolean extended = BlockStateValues.getPistonValues().get(blockState); + // 1f if extended, otherwise 0f + tagBuilder.putFloat("Progress", (extended) ? 1.0f : 0.0f); + // 1 if sticky, 0 if not + tagBuilder.putByte("Sticky", (byte) ((BlockStateValues.isStickyPiston(blockState)) ? 1 : 0)); + return tagBuilder.build(); } - } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/ShulkerBoxBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/ShulkerBoxBlockEntityTranslator.java index 08e3abaa..69fa1084 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/ShulkerBoxBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/ShulkerBoxBlockEntityTranslator.java @@ -26,23 +26,18 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.nukkitx.nbt.NbtMapBuilder; import org.geysermc.connector.network.translators.world.block.BlockStateValues; -import java.util.HashMap; -import java.util.Map; - @BlockEntity(name = "ShulkerBox", regex = "shulker_box") public class ShulkerBoxBlockEntityTranslator extends BlockEntityTranslator { - @Override - public Map translateTag(CompoundTag tag, int blockState) { - Map tags = new HashMap<>(); - + public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { byte direction = BlockStateValues.getShulkerBoxDirection(blockState); // Just in case... - if (direction == -1) direction = 1; - tags.put("facing", direction); - return tags; + if (direction == -1) { + direction = 1; + } + builder.put("facing", direction); } - } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java index b40ed42c..a4f800da 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java @@ -27,51 +27,12 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.mc.protocol.data.message.MessageSerializer; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.nukkitx.nbt.NbtMapBuilder; import org.geysermc.connector.utils.MessageUtils; import org.geysermc.connector.utils.SignUtils; -import java.util.HashMap; -import java.util.Map; - @BlockEntity(name = "Sign", regex = "sign") public class SignBlockEntityTranslator extends BlockEntityTranslator { - - @Override - public Map translateTag(CompoundTag tag, int blockState) { - Map tags = new HashMap<>(); - - StringBuilder signText = new StringBuilder(); - for(int i = 0; i < 4; i++) { - int currentLine = i+1; - String signLine = getOrDefault(tag.getValue().get("Text" + currentLine), ""); - signLine = MessageUtils.getBedrockMessage(MessageSerializer.fromString(signLine)); - - // Check the character width on the sign to ensure there is no overflow that is usually hidden - // to Java Edition clients but will appear to Bedrock clients - int signWidth = 0; - StringBuilder finalSignLine = new StringBuilder(); - for (char c : signLine.toCharArray()) { - signWidth += SignUtils.getCharacterWidth(c); - if (signWidth <= SignUtils.BEDROCK_CHARACTER_WIDTH_MAX) { - finalSignLine.append(c); - } else { - break; - } - } - - // Java Edition 1.14 added the ability to change the text color of the whole sign using dye - if (tag.contains("Color")) { - signText.append(getBedrockSignColor(tag.get("Color").getValue().toString())); - } - - signText.append(finalSignLine.toString()); - signText.append("\n"); - } - - tags.put("Text", MessageUtils.getBedrockMessage(MessageSerializer.fromString(signText.toString()))); - return tags; - } - /** * Maps a color stored in a sign's Color tag to a Bedrock Edition formatting code. *
@@ -133,4 +94,36 @@ public class SignBlockEntityTranslator extends BlockEntityTranslator { return base; } + @Override + public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { + StringBuilder signText = new StringBuilder(); + for (int i = 0; i < 4; i++) { + int currentLine = i + 1; + String signLine = getOrDefault(tag.getValue().get("Text" + currentLine), ""); + signLine = MessageUtils.getBedrockMessage(MessageSerializer.fromString(signLine)); + + // Check the character width on the sign to ensure there is no overflow that is usually hidden + // to Java Edition clients but will appear to Bedrock clients + int signWidth = 0; + StringBuilder finalSignLine = new StringBuilder(); + for (char c : signLine.toCharArray()) { + signWidth += SignUtils.getCharacterWidth(c); + if (signWidth <= SignUtils.BEDROCK_CHARACTER_WIDTH_MAX) { + finalSignLine.append(c); + } else { + break; + } + } + + // Java Edition 1.14 added the ability to change the text color of the whole sign using dye + if (tag.contains("Color")) { + signText.append(getBedrockSignColor(tag.get("Color").getValue().toString())); + } + + signText.append(finalSignLine.toString()); + signText.append("\n"); + } + + builder.put("Text", MessageUtils.getBedrockMessage(MessageSerializer.fromString(signText.toString()))); + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java index 6d350c0c..c5f47994 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java @@ -25,29 +25,26 @@ package org.geysermc.connector.network.translators.world.block.entity; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.nukkitx.nbt.NbtMapBuilder; import org.geysermc.connector.network.translators.world.block.BlockStateValues; -import java.util.HashMap; -import java.util.Map; - @BlockEntity(name = "Skull", regex = "skull") public class SkullBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { - @Override public boolean isBlock(int blockState) { return BlockStateValues.getSkullVariant(blockState) != -1; } @Override - public Map translateTag(com.github.steveice10.opennbt.tag.builtin.CompoundTag tag, int blockState) { - Map tags = new HashMap<>(); + public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { byte skullVariant = BlockStateValues.getSkullVariant(blockState); float rotation = BlockStateValues.getSkullRotation(blockState) * 22.5f; // Just in case... - if (skullVariant == -1) skullVariant = 0; - tags.put("Rotation", rotation); - tags.put("SkullType", skullVariant); - return tags; + if (skullVariant == -1) { + skullVariant = 0; + } + builder.put("Rotation", rotation); + builder.put("SkullType", skullVariant); } - } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SpawnerBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SpawnerBlockEntityTranslator.java index 2601e3de..38507f54 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SpawnerBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SpawnerBlockEntityTranslator.java @@ -26,63 +26,58 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; +import com.nukkitx.nbt.NbtMapBuilder; import org.geysermc.connector.entity.type.EntityType; -import java.util.HashMap; -import java.util.Map; - @BlockEntity(name = "MobSpawner", regex = "mob_spawner") public class SpawnerBlockEntityTranslator extends BlockEntityTranslator { - @Override - public Map translateTag(CompoundTag tag, int blockState) { - Map tags = new HashMap<>(); + public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { + Tag current; - if (tag.get("MaxNearbyEntities") != null) { - tags.put("MaxNearbyEntities", (short) tag.get("MaxNearbyEntities").getValue()); + if ((current = tag.get("MaxNearbyEntities")) != null) { + builder.put("MaxNearbyEntities", current.getValue()); } - if (tag.get("RequiredPlayerRange") != null) { - tags.put("RequiredPlayerRange", (short) tag.get("RequiredPlayerRange").getValue()); + if ((current = tag.get("RequiredPlayerRange")) != null) { + builder.put("RequiredPlayerRange", current.getValue()); } - if (tag.get("SpawnCount") != null) { - tags.put("SpawnCount", (short) tag.get("SpawnCount").getValue()); + if ((current = tag.get("SpawnCount")) != null) { + builder.put("SpawnCount", current.getValue()); } - if (tag.get("MaxSpawnDelay") != null) { - tags.put("MaxSpawnDelay", (short) tag.get("MaxSpawnDelay").getValue()); + if ((current = tag.get("MaxSpawnDelay")) != null) { + builder.put("MaxSpawnDelay", current.getValue()); } - if (tag.get("Delay") != null) { - tags.put("Delay", (short) tag.get("Delay").getValue()); + if ((current = tag.get("Delay")) != null) { + builder.put("Delay", current.getValue()); } - if (tag.get("SpawnRange") != null) { - tags.put("SpawnRange", (short) tag.get("SpawnRange").getValue()); + if ((current = tag.get("SpawnRange")) != null) { + builder.put("SpawnRange", current.getValue()); } - if (tag.get("MinSpawnDelay") != null) { - tags.put("MinSpawnDelay", (short) tag.get("MinSpawnDelay").getValue()); + if ((current = tag.get("MinSpawnDelay")) != null) { + builder.put("MinSpawnDelay", current.getValue()); } - if (tag.get("SpawnData") != null) { - CompoundTag spawnData = tag.get("SpawnData"); + CompoundTag spawnData = tag.get("SpawnData"); + if (spawnData != null) { String entityID = (String) spawnData.get("id").getValue(); - tags.put("EntityIdentifier", entityID); + builder.put("EntityIdentifier", entityID); EntityType type = EntityType.getFromIdentifier(entityID); if (type != null) { - tags.put("DisplayEntityWidth", type.getWidth()); - tags.put("DisplayEntityHeight", type.getHeight()); - tags.put("DisplayEntityScale", 1.0f); + builder.put("DisplayEntityWidth", type.getWidth()); + builder.put("DisplayEntityHeight", type.getHeight()); + builder.put("DisplayEntityScale", 1.0f); } } - tags.put("id", "MobSpawner"); - tags.put("isMovable", (byte) 1); - - return tags; + builder.put("id", "MobSpawner"); + builder.put("isMovable", (byte) 1); } - } diff --git a/connector/src/main/java/org/geysermc/connector/utils/BlockEntityUtils.java b/connector/src/main/java/org/geysermc/connector/utils/BlockEntityUtils.java index 0ae31b33..e8fd8291 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/BlockEntityUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/BlockEntityUtils.java @@ -33,17 +33,17 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.block.entity.BlockEntityTranslator; public class BlockEntityUtils { - private static final BlockEntityTranslator EMPTY_TRANSLATOR = BlockEntityTranslator.BLOCK_ENTITY_TRANSLATORS.get("Empty"); public static String getBedrockBlockEntityId(String id) { // These are the only exceptions when it comes to block entity ids - if (BlockEntityTranslator.BLOCK_ENTITY_TRANSLATIONS.containsKey(id)) { - return BlockEntityTranslator.BLOCK_ENTITY_TRANSLATIONS.get(id); + String value = BlockEntityTranslator.BLOCK_ENTITY_TRANSLATIONS.get(id); + if (value != null) { + return value; } id = id.replace("minecraft:", "") - .replace("_", " "); + .replace("_", " "); // Split at every space or capital letter - for the latter, some legacy Java block entity tags are the correct format already String[] words; if (!id.toUpperCase().equals(id)) { // Otherwise we get [S, K, U, L, L] @@ -60,11 +60,10 @@ public class BlockEntityUtils { public static BlockEntityTranslator getBlockEntityTranslator(String name) { BlockEntityTranslator blockEntityTranslator = BlockEntityTranslator.BLOCK_ENTITY_TRANSLATORS.get(name); - if (blockEntityTranslator == null) { - return EMPTY_TRANSLATOR; + if (blockEntityTranslator != null) { + return blockEntityTranslator; } - - return blockEntityTranslator; + return EMPTY_TRANSLATOR; } public static void updateBlockEntity(GeyserSession session, NbtMap blockEntity, Position position) { diff --git a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java index a63eeb42..0769a4d1 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java @@ -72,9 +72,9 @@ import static org.geysermc.connector.network.translators.world.block.BlockTransl @UtilityClass public class ChunkUtils { - /** - * Temporarily stores positions of BlockState values that are needed for certain block entities actively + * Temporarily stores positions of BlockState values that are needed for certain block entities actively. + * Not used if cache chunks is enabled */ public static final Object2IntMap CACHED_BLOCK_ENTITIES = new Object2IntOpenHashMap<>(); @@ -300,11 +300,16 @@ public class ChunkUtils { public static void updateBlock(GeyserSession session, int blockState, Vector3i position) { // Checks for item frames so they aren't tripped up and removed - if (ItemFrameEntity.positionContainsItemFrame(session, position) && blockState == AIR) { - ((ItemFrameEntity) session.getEntityCache().getEntityByJavaId(ItemFrameEntity.getItemFrameEntityId(session, position))).updateBlock(session); - return; - } else if (ItemFrameEntity.positionContainsItemFrame(session, position)) { - Entity entity = session.getEntityCache().getEntityByJavaId(ItemFrameEntity.getItemFrameEntityId(session, position)); + long frameEntityId = ItemFrameEntity.getItemFrameEntityId(session, position); + if (frameEntityId != -1) { + // TODO: Very occasionally the item frame doesn't sync up when destroyed + Entity entity = session.getEntityCache().getEntityByJavaId(frameEntityId); + if (blockState == AIR && entity != null) { // Item frame is still present and no block overrides that; refresh it + ((ItemFrameEntity) entity).updateBlock(session); + return; + } + + // Otherwise the item frame is gone if (entity != null) { session.getEntityCache().removeEntity(entity, false); } else { @@ -342,7 +347,10 @@ public class ChunkUtils { ((BedrockOnlyBlockEntity) requiresBlockState).updateBlock(session, blockState, position); break; } - CACHED_BLOCK_ENTITIES.put(new Position(position.getX(), position.getY(), position.getZ()), blockState); + if (!session.getConnector().getConfig().isCacheChunks()) { + // Blocks aren't saved to a chunk cache; resort to this smaller cache + CACHED_BLOCK_ENTITIES.put(new Position(position.getX(), position.getY(), position.getZ()), blockState); + } break; //No block will be a part of two classes } } From 0e15aa7441eff940c8b5f0bbb475eb46b5c0af15 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Thu, 5 Nov 2020 18:42:33 -0500 Subject: [PATCH 100/205] Fireball and ghast improvements (#1469) * Fireball and ghast improvements - Ghasts now visually show if they're charging a fireball - Fireballs are now vastly better and will update better * Add gravity and drag to projectiles * Add check for session close and improve fireball * Remove motion stuff from fireball * Make fireball hittable * Add wither skull entity * Small changes * Add note about laggy fireballs Co-authored-by: David Choo --- .../entity/ItemedFireballEntity.java | 18 +++- .../connector/entity/ThrowableEntity.java | 97 ++++++++++++++++--- .../connector/entity/WitherSkullEntity.java | 58 +++++++++++ .../entity/living/monster/GhastEntity.java | 49 ++++++++++ .../connector/entity/type/EntityType.java | 6 +- 5 files changed, 212 insertions(+), 16 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/entity/WitherSkullEntity.java create mode 100644 connector/src/main/java/org/geysermc/connector/entity/living/monster/GhastEntity.java diff --git a/connector/src/main/java/org/geysermc/connector/entity/ItemedFireballEntity.java b/connector/src/main/java/org/geysermc/connector/entity/ItemedFireballEntity.java index e04e0411..1544f767 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ItemedFireballEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/ItemedFireballEntity.java @@ -27,10 +27,24 @@ package org.geysermc.connector.entity; import com.nukkitx.math.vector.Vector3f; import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; -public class ItemedFireballEntity extends Entity { +public class ItemedFireballEntity extends ThrowableEntity { + private final Vector3f acceleration; public ItemedFireballEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); + super(entityId, geyserId, entityType, position, Vector3f.ZERO, rotation); + acceleration = motion; + } + + @Override + protected void updatePosition(GeyserSession session) { + position = position.add(motion); + // TODO: While this reduces latency in position updating (needed for better fireball reflecting), + // TODO: movement is incredibly stiff. See if the MoveEntityDeltaPacket in 1.16.100 fixes this, and if not, + // TODO: only use this laggy movement for fireballs that be reflected + moveAbsoluteImmediate(session, position, rotation, false, true); + float drag = getDrag(session); + motion = motion.add(acceleration).mul(drag); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java b/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java index b3632606..5b7ba5c0 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java @@ -31,14 +31,23 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +/** + * Used as a class for any object-like entity that moves as a projectile + */ public class ThrowableEntity extends Entity { private Vector3f lastPosition; - private ScheduledFuture positionUpdater; + /** + * Updates the position for the Bedrock client. + * + * Java clients assume the next positions of moving items. Bedrock needs to be explicitly told positions + */ + protected ScheduledFuture positionUpdater; public ThrowableEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); @@ -49,20 +58,86 @@ public class ThrowableEntity extends Entity { public void spawnEntity(GeyserSession session) { super.spawnEntity(session); positionUpdater = session.getConnector().getGeneralThreadPool().scheduleAtFixedRate(() -> { - super.moveRelative(session, motion.getX(), motion.getY(), motion.getZ(), rotation, onGround); - - if (metadata.getFlags().getFlag(EntityFlag.HAS_GRAVITY)) { - float gravity = 0.03f; // Snowball, Egg, and Ender Pearl - if (entityType == EntityType.THROWN_POTION || entityType == EntityType.LINGERING_POTION) { - gravity = 0.05f; - } else if (entityType == EntityType.THROWN_EXP_BOTTLE) { - gravity = 0.07f; - } - motion = motion.down(gravity); + if (session.isClosed()) { + positionUpdater.cancel(true); + return; } + updatePosition(session); }, 0, 50, TimeUnit.MILLISECONDS); } + protected void moveAbsoluteImmediate(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) { + super.moveAbsolute(session, position, rotation, isOnGround, teleported); + } + + protected void updatePosition(GeyserSession session) { + super.moveRelative(session, motion.getX(), motion.getY(), motion.getZ(), rotation, onGround); + float drag = getDrag(session); + float gravity = getGravity(); + motion = motion.mul(drag).down(gravity); + } + + /** + * Get the gravity of this entity type. Used for applying gravity while the entity is in motion. + * + * @return the amount of gravity to apply to this entity while in motion. + */ + protected float getGravity() { + if (metadata.getFlags().getFlag(EntityFlag.HAS_GRAVITY)) { + switch (entityType) { + case THROWN_POTION: + case LINGERING_POTION: + return 0.05f; + case THROWN_EXP_BOTTLE: + return 0.07f; + case FIREBALL: + return 0; + case SNOWBALL: + case THROWN_EGG: + case THROWN_ENDERPEARL: + return 0.03f; + } + } + return 0; + } + + /** + * @param session the session of the Bedrock client. + * @return the drag that should be multiplied to the entity's motion + */ + protected float getDrag(GeyserSession session) { + if (isInWater(session)) { + return 0.8f; + } else { + switch (entityType) { + case THROWN_POTION: + case LINGERING_POTION: + case THROWN_EXP_BOTTLE: + case SNOWBALL: + case THROWN_EGG: + case THROWN_ENDERPEARL: + return 0.99f; + case FIREBALL: + case SMALL_FIREBALL: + case DRAGON_FIREBALL: + return 0.95f; + } + } + return 1; + } + + /** + * @param session the session of the Bedrock client. + * @return true if this entity is currently in water. + */ + protected boolean isInWater(GeyserSession session) { + if (session.getConnector().getConfig().isCacheChunks()) { + int block = session.getConnector().getWorldManager().getBlockAt(session, position.toInt()); + return block == BlockTranslator.BEDROCK_WATER_ID; + } + return false; + } + @Override public boolean despawnEntity(GeyserSession session) { positionUpdater.cancel(true); diff --git a/connector/src/main/java/org/geysermc/connector/entity/WitherSkullEntity.java b/connector/src/main/java/org/geysermc/connector/entity/WitherSkullEntity.java new file mode 100644 index 00000000..99b3df3d --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/WitherSkullEntity.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.entity; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class WitherSkullEntity extends ItemedFireballEntity { + private boolean isCharged; + + public WitherSkullEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + protected float getDrag(GeyserSession session) { + return isCharged ? 0.73f : super.getDrag(session); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 7) { + boolean newIsCharged = (boolean) entityMetadata.getValue(); + if (newIsCharged != isCharged) { + isCharged = newIsCharged; + entityType = isCharged ? EntityType.WITHER_SKULL_DANGEROUS : EntityType.WITHER_SKULL; + despawnEntity(session); + spawnEntity(session); + } + } + super.updateBedrockMetadata(entityMetadata, session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/GhastEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/GhastEntity.java new file mode 100644 index 00000000..3d3be87c --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/GhastEntity.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.entity.living.monster; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import org.geysermc.connector.entity.living.FlyingEntity; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class GhastEntity extends FlyingEntity { + + public GhastEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 15) { + // If the ghast is attacking + metadata.put(EntityData.CHARGE_AMOUNT, (byte) ((boolean) entityMetadata.getValue() ? 1 : 0)); + } + super.updateBedrockMetadata(entityMetadata, session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java index fddab5a4..7557bd04 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java +++ b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java @@ -74,7 +74,7 @@ public enum EntityType { ENDERMAN(EndermanEntity.class, 38, 2.9f, 0.6f), SILVERFISH(MonsterEntity.class, 39, 0.3f, 0.4f), CAVE_SPIDER(MonsterEntity.class, 40, 0.5f, 0.7f), - GHAST(FlyingEntity.class, 41, 4.0f), + GHAST(GhastEntity.class, 41, 4.0f), MAGMA_CUBE(MagmaCubeEntity.class, 42, 0.51f), BLAZE(BlazeEntity.class, 43, 1.8f, 0.6f), ZOMBIE_VILLAGER(ZombieEntity.class, 44, 1.8f, 0.6f, 0.6f, 1.62f), @@ -125,9 +125,9 @@ public enum EntityType { THROWN_POTION(ThrowableEntity.class, 86, 0.25f, 0.25f, 0.25f, 0f, "minecraft:splash_potion"), THROWN_ENDERPEARL(ThrowableEntity.class, 87, 0.25f, 0.25f, 0.25f, 0f, "minecraft:ender_pearl"), LEASH_KNOT(LeashKnotEntity.class, 88, 0.5f, 0.375f), - WITHER_SKULL(Entity.class, 89, 0.3125f), + WITHER_SKULL(WitherSkullEntity.class, 89, 0.3125f), BOAT(BoatEntity.class, 90, 0.7f, 1.6f, 1.6f, 0.35f), - WITHER_SKULL_DANGEROUS(Entity.class, 91, 0f), + WITHER_SKULL_DANGEROUS(WitherSkullEntity.class, 91, 0f), LIGHTNING_BOLT(Entity.class, 93, 0f), SMALL_FIREBALL(ItemedFireballEntity.class, 94, 0.3125f), AREA_EFFECT_CLOUD(AreaEffectCloudEntity.class, 95, 0.5f, 1.0f), From a6cc28ee80072b57b4f6793cc22a6ad6bfd8c90c Mon Sep 17 00:00:00 2001 From: rtm516 Date: Sat, 7 Nov 2020 00:52:09 +0000 Subject: [PATCH 101/205] Add config option for enabling achievements (#1504) * Add config option for enabling achievements * Disabled achievements by default and added warning about commands being disabled * Update config.yml * Rename achievements-enabled to xbox-achievements-enabled for clarity Co-authored-by: Camotoy <20743703+DoctorMacc@users.noreply.github.com> --- .../connector/configuration/GeyserConfiguration.java | 2 ++ .../connector/configuration/GeyserJacksonConfiguration.java | 3 +++ .../geysermc/connector/network/session/GeyserSession.java | 4 ++-- connector/src/main/resources/config.yml | 5 +++++ 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java b/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java index c1cc4d03..153a9174 100644 --- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java +++ b/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java @@ -81,6 +81,8 @@ public interface GeyserConfiguration { boolean isForceResourcePacks(); + boolean isXboxAchievementsEnabled(); + int getCacheImages(); IMetricsInfo getMetrics(); diff --git a/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java b/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java index 45676fbd..d19cfe49 100644 --- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java +++ b/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java @@ -107,6 +107,9 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration @JsonProperty("force-resource-packs") private boolean forceResourcePacks = true; + @JsonProperty("xbox-achievements-enabled") + private boolean xboxAchievementsEnabled = false; + private MetricsInfo metrics = new MetricsInfo(); @Getter diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 6a2913c8..4429268a 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -634,7 +634,7 @@ public class GeyserSession implements CommandSender { startGamePacket.setLevelGameType(GameType.SURVIVAL); startGamePacket.setDifficulty(1); startGamePacket.setDefaultSpawn(Vector3i.ZERO); - startGamePacket.setAchievementsDisabled(true); + startGamePacket.setAchievementsDisabled(!connector.getConfig().isXboxAchievementsEnabled()); startGamePacket.setCurrentTick(-1); startGamePacket.setEduEditionOffers(0); startGamePacket.setEduFeaturesEnabled(false); @@ -645,7 +645,7 @@ public class GeyserSession implements CommandSender { startGamePacket.getGamerules().add(new GameRuleData<>("showcoordinates", true)); startGamePacket.setPlatformBroadcastMode(GamePublishSetting.PUBLIC); startGamePacket.setXblBroadcastMode(GamePublishSetting.PUBLIC); - startGamePacket.setCommandsEnabled(true); + startGamePacket.setCommandsEnabled(!connector.getConfig().isXboxAchievementsEnabled()); startGamePacket.setTexturePacksRequired(false); startGamePacket.setBonusChestEnabled(false); startGamePacket.setStartingWithMap(false); diff --git a/connector/src/main/resources/config.yml b/connector/src/main/resources/config.yml index 43e3e8ed..b53fcdd0 100644 --- a/connector/src/main/resources/config.yml +++ b/connector/src/main/resources/config.yml @@ -117,6 +117,11 @@ above-bedrock-nether-building: false # want to download the resource packs force-resource-packs: true +# Allows Xbox achievements to be unlocked. +# This disables certain commands so the Bedrock client can't to "cheat" to get them. +# Commands such as /gamemode and /give will not work from Bedrock with this enabled +xbox-achievements-enabled: false + # bStats is a stat tracker that is entirely anonymous and tracks only basic information # about Geyser, such as how many people are online, how many servers are using Geyser, # what OS is being used, etc. You can learn more about bStats here: https://bstats.org/. From c7654ff98c3e25bf0e273b513f4a0b37088d0e31 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Sat, 7 Nov 2020 18:47:45 +0000 Subject: [PATCH 102/205] Disable the sponsor button (#1509) --- .github/FUNDING.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index b4b93dca..19b655c2 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,7 +1,7 @@ # These are supported funding model platforms github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] -patreon: GeyserMC +patreon: #GeyserMC # Disabled currently open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel From 2d95302b1014ef53357c0f3e29cf91ca95634c1d Mon Sep 17 00:00:00 2001 From: rtm516 Date: Sat, 7 Nov 2020 19:17:17 +0000 Subject: [PATCH 103/205] Add support for passing config options as arguments (#1506) --- .../standalone/GeyserStandaloneBootstrap.java | 151 +++++++++++++++++- connector/src/main/resources/languages | 2 +- 2 files changed, 149 insertions(+), 4 deletions(-) diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java index 123a9a60..f58a997e 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java @@ -25,6 +25,11 @@ package org.geysermc.platform.standalone; +import com.fasterxml.jackson.databind.BeanDescription; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.introspect.AnnotatedField; +import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition; import lombok.Getter; import net.minecrell.terminalconsole.TerminalConsoleAppender; import org.apache.logging.log4j.LogManager; @@ -36,6 +41,7 @@ import org.geysermc.connector.bootstrap.GeyserBootstrap; import org.geysermc.connector.command.CommandManager; import org.geysermc.connector.common.PlatformType; import org.geysermc.connector.configuration.GeyserConfiguration; +import org.geysermc.connector.configuration.GeyserJacksonConfiguration; import org.geysermc.connector.dump.BootstrapDumpInfo; import org.geysermc.connector.ping.GeyserLegacyPingPassthrough; import org.geysermc.connector.ping.IGeyserPingPassthrough; @@ -50,7 +56,8 @@ import java.lang.reflect.Method; import java.nio.file.Path; import java.nio.file.Paths; import java.text.MessageFormat; -import java.util.UUID; +import java.util.*; +import java.util.stream.Collectors; public class GeyserStandaloneBootstrap implements GeyserBootstrap { @@ -67,6 +74,9 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { private GeyserConnector connector; + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + private static final Map argsConfigKeys = new HashMap<>(); public static void main(String[] args) { GeyserStandaloneBootstrap bootstrap = new GeyserStandaloneBootstrap(); @@ -74,6 +84,8 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { boolean useGuiOpts = bootstrap.useGui; String configFilenameOpt = bootstrap.configFilename; + List availableProperties = getPOJOForClass(GeyserJacksonConfiguration.class); + for (int i = 0; i < args.length; i++) { // By default, standalone Geyser will check if it should open the GUI based on if the GUI is null // Optionally, you can force the use of a GUI or no GUI by specifying args @@ -106,8 +118,43 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { System.out.println(" --gui, --nogui " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.gui")); return; default: - String badArgMsg = LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.unrecognised"); - System.err.println(MessageFormat.format(badArgMsg, arg)); + // We have likely added a config option argument + if (arg.startsWith("--")) { + // Split the argument by an = + String[] argParts = arg.substring(2).split("="); + if (argParts.length == 2) { + // Split the config key by . to allow for nested options + String[] configKeyParts = argParts[0].split("\\."); + + // Loop the possible config options to check the passed key is valid + boolean found = false; + for (BeanPropertyDefinition property : availableProperties) { + if (configKeyParts[0].equals(property.getName())) { + if (configKeyParts.length > 1) { + // Loop sub-section options to check the passed key is valid + for (BeanPropertyDefinition subProperty : getPOJOForClass(property.getRawPrimaryType())) { + if (configKeyParts[1].equals(subProperty.getName())) { + found = true; + break; + } + } + } else { + found = true; + } + + break; + } + } + + // Add the found key to the stored list for later usage + if (found) { + argsConfigKeys.put(argParts[0], argParts[1]); + break; + } + } + } + + System.err.println(LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.unrecognised", arg)); return; } } @@ -148,6 +195,9 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { try { File configFile = FileUtils.fileOrCopiedFromResource(new File(configFilename), "config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString())); geyserConfig = FileUtils.loadConfig(configFile, GeyserStandaloneConfiguration.class); + + handleArgsConfigOptions(); + if (this.geyserConfig.getRemote().getAddress().equalsIgnoreCase("auto")) { geyserConfig.setAutoconfiguredRemote(true); // Doesn't really need to be set but /shrug geyserConfig.getRemote().setAddress("127.0.0.1"); @@ -223,4 +273,99 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { public BootstrapDumpInfo getDumpInfo() { return new GeyserStandaloneDumpInfo(this); } + + /** + * Get the {@link BeanPropertyDefinition}s for the given class + * + * @param clazz The class to get the definitions for + * @return A list of {@link BeanPropertyDefinition} for the given class + */ + public static List getPOJOForClass(Class clazz) { + JavaType javaType = OBJECT_MAPPER.getTypeFactory().constructType(clazz); + + // Introspect the given type + BeanDescription beanDescription = OBJECT_MAPPER.getSerializationConfig().introspect(javaType); + + // Find properties + List properties = beanDescription.findProperties(); + + // Get the ignored properties + Set ignoredProperties = OBJECT_MAPPER.getSerializationConfig().getAnnotationIntrospector() + .findPropertyIgnorals(beanDescription.getClassInfo()).getIgnored(); + + // Filter properties removing the ignored ones + return properties.stream() + .filter(property -> !ignoredProperties.contains(property.getName())) + .collect(Collectors.toList()); + } + + /** + * Set a POJO property value on an object + * + * @param property The {@link BeanPropertyDefinition} to set + * @param parentObject The object to alter + * @param value The new value of the property + */ + private static void setConfigOption(BeanPropertyDefinition property, Object parentObject, Object value) { + Object parsedValue = value; + + // Change the values type if needed + if (int.class.equals(property.getRawPrimaryType())) { + parsedValue = Integer.valueOf((String) parsedValue); + } else if (boolean.class.equals(property.getRawPrimaryType())) { + parsedValue = Boolean.valueOf((String) parsedValue); + } + + // Force the value to be set + AnnotatedField field = property.getField(); + field.fixAccess(true); + field.setValue(parentObject, parsedValue); + } + + /** + * Update the loaded {@link GeyserStandaloneConfiguration} with any values passed in the command line arguments + */ + private void handleArgsConfigOptions() { + // Get the available properties from the class + List availableProperties = getPOJOForClass(GeyserJacksonConfiguration.class); + + for (Map.Entry configKey : argsConfigKeys.entrySet()) { + String[] configKeyParts = configKey.getKey().split("\\."); + + // Loop over the properties looking for any matches against the stored one from the argument + for (BeanPropertyDefinition property : availableProperties) { + if (configKeyParts[0].equals(property.getName())) { + if (configKeyParts.length > 1) { + // Loop through the sub property if the first part matches + for (BeanPropertyDefinition subProperty : getPOJOForClass(property.getRawPrimaryType())) { + if (configKeyParts[1].equals(subProperty.getName())) { + geyserLogger.info(LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.set_config_option", configKey.getKey(), configKey.getValue())); + + // Set the sub property value on the config + try { + Object subConfig = property.getGetter().callOn(geyserConfig); + setConfigOption(subProperty, subConfig, configKey.getValue()); + } catch (Exception e) { + geyserLogger.error("Failed to set config option: " + property.getFullName()); + } + + break; + } + } + } else { + geyserLogger.info(LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.set_config_option", configKey.getKey(), configKey.getValue())); + + // Set the property value on the config + try { + setConfigOption(property, geyserConfig, configKey.getValue()); + } catch (Exception e) { + geyserLogger.error("Failed to set config option: " + property.getFullName()); + } + } + + break; + } + } + } + } } diff --git a/connector/src/main/resources/languages b/connector/src/main/resources/languages index b7ef31bd..bf4b0b71 160000 --- a/connector/src/main/resources/languages +++ b/connector/src/main/resources/languages @@ -1 +1 @@ -Subproject commit b7ef31bd9c45aa3a0735883764c231f30cb55bfa +Subproject commit bf4b0b7103193154dd0b06e0459dc375c753069a From e00715ceabf9246cbfc63e72a559a1953f5fc156 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Mon, 9 Nov 2020 10:34:27 +0100 Subject: [PATCH 104/205] Fixed some issues related to Scoreboards (#1446) * Fixes some issues related to Scoreboard Teams * The cached Score info should update no matter what kind of update the Team got. * Team entities specified at the create Team packet should also be checked if they exist as Score in the registered Objectives * Rewrote some Scoreboard code and fixed various issues * Minor formatting changes --- .../connector/entity/PlayerEntity.java | 17 +- .../JavaDisplayScoreboardTranslator.java | 5 +- .../JavaScoreboardObjectiveTranslator.java | 17 +- .../connector/scoreboard/Objective.java | 62 ++++--- .../geysermc/connector/scoreboard/Score.java | 99 +++++++++--- .../connector/scoreboard/Scoreboard.java | 135 +++++++++------- .../geysermc/connector/scoreboard/Team.java | 152 +++++++++++++++--- 7 files changed, 333 insertions(+), 154 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java index 390110d1..bfadd83c 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java @@ -27,7 +27,6 @@ package org.geysermc.connector.entity; import com.github.steveice10.mc.auth.data.GameProfile; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.github.steveice10.mc.protocol.data.game.scoreboard.NameTagVisibility; import com.github.steveice10.mc.protocol.data.message.TextMessage; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.math.vector.Vector3f; @@ -235,18 +234,12 @@ public class PlayerEntity extends LivingEntity { } Team team = session.getWorldCache().getScoreboard().getTeamFor(username); if (team != null) { - // Cover different visibility settings - if (team.getNameTagVisibility() == NameTagVisibility.NEVER) { - metadata.put(EntityData.NAMETAG, ""); - } else if (team.getNameTagVisibility() == NameTagVisibility.HIDE_FOR_OTHER_TEAMS && - !team.getEntities().contains(session.getPlayerEntity().getUsername())) { - metadata.put(EntityData.NAMETAG, ""); - } else if (team.getNameTagVisibility() == NameTagVisibility.HIDE_FOR_OWN_TEAM && - team.getEntities().contains(session.getPlayerEntity().getUsername())) { - metadata.put(EntityData.NAMETAG, ""); - } else { - metadata.put(EntityData.NAMETAG, team.getPrefix() + MessageUtils.toChatColor(team.getColor()) + username + team.getSuffix()); + String displayName = ""; + if (team.isVisibleFor(session.getPlayerEntity().getUsername())) { + displayName = MessageUtils.toChatColor(team.getColor()) + username; + displayName = team.getCurrentData().getDisplayName(displayName); } + metadata.put(EntityData.NAMETAG, displayName); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaDisplayScoreboardTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaDisplayScoreboardTranslator.java index 3ee174d7..b42ac78e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaDisplayScoreboardTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaDisplayScoreboardTranslator.java @@ -36,8 +36,7 @@ public class JavaDisplayScoreboardTranslator extends PacketTranslator cachedData.updateTime || + (currentData.team != null && currentData.team.shouldUpdate()); + } + + public void update(String objectiveName) { + if (cachedData == null) { + cachedData = new ScoreData(); + cachedData.updateType = UpdateType.ADD; + if (currentData.updateType == UpdateType.REMOVE) { + cachedData.updateType = UpdateType.REMOVE; + } + } else { + cachedData.updateType = currentData.updateType; + } + + cachedData.updateTime = currentData.updateTime; + cachedData.team = currentData.team; + cachedData.score = currentData.score; + + String name = this.name; + if (cachedData.team != null) { + cachedData.team.prepareUpdate(); + name = cachedData.team.getDisplayName(name); + } + cachedInfo = new ScoreInfo(id, objectiveName, cachedData.score, name); + } + + @Getter + public static final class ScoreData { + protected UpdateType updateType; + protected long updateTime; + + private Team team; + private int score; + + protected ScoreData() { + updateType = UpdateType.ADD; + } } } diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java b/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java index 732a056e..8eaa2e27 100644 --- a/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java +++ b/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java @@ -43,7 +43,7 @@ import java.util.concurrent.atomic.AtomicLong; import static org.geysermc.connector.scoreboard.UpdateType.*; @Getter -public class Scoreboard { +public final class Scoreboard { private final GeyserSession session; private final GeyserLogger logger; private final AtomicLong nextId = new AtomicLong(0); @@ -51,7 +51,8 @@ public class Scoreboard { private final Map objectives = new ConcurrentHashMap<>(); private final Map teams = new HashMap<>(); - private int lastScoreCount = 0; + private int lastAddScoreCount = 0; + private int lastRemoveScoreCount = 0; public Scoreboard(GeyserSession session) { this.session = session; @@ -59,19 +60,21 @@ public class Scoreboard { } public Objective registerNewObjective(String objectiveId, boolean active) { - if (active || objectives.containsKey(objectiveId)) { - return objectives.get(objectiveId); + Objective objective = objectives.get(objectiveId); + if (active || objective != null) { + return objective; } - Objective objective = new Objective(this, objectiveId); + objective = new Objective(this, objectiveId); objectives.put(objectiveId, objective); return objective; } - public Objective registerNewObjective(String objectiveId, ScoreboardPosition displaySlot) { + public Objective displayObjective(String objectiveId, ScoreboardPosition displaySlot) { Objective objective = objectives.get(objectiveId); if (objective != null) { if (!objective.isActive()) { objective.setActive(displaySlot); + removeOldObjectives(objective); return objective; } despawnObjective(objective); @@ -79,9 +82,21 @@ public class Scoreboard { objective = new Objective(this, objectiveId, displaySlot, "unknown", 0); objectives.put(objectiveId, objective); + removeOldObjectives(objective); return objective; } + private void removeOldObjectives(Objective newObjective) { + for (Objective next : objectives.values()) { + if (next.getId() == newObjective.getId()) { + continue; + } + if (next.getDisplaySlot() == newObjective.getDisplaySlot()) { + next.setUpdateType(REMOVE); + } + } + } + public Team registerNewTeam(String teamName, Set players) { Team team = teams.get(teamName); if (team != null) { @@ -89,7 +104,7 @@ public class Scoreboard { return team; } - team = new Team(this, teamName).setEntities(players); + team = new Team(this, teamName).addEntities(players); teams.put(teamName, team); return team; } @@ -117,8 +132,9 @@ public class Scoreboard { } public void onUpdate() { - List addScores = new ArrayList<>(getLastScoreCount()); - List removeScores = new ArrayList<>(getLastScoreCount()); + List addScores = new ArrayList<>(getLastAddScoreCount()); + List removeScores = new ArrayList<>(getLastRemoveScoreCount()); + List removedObjectives = new ArrayList<>(); for (Objective objective : objectives.values()) { if (!objective.isActive()) { @@ -129,65 +145,58 @@ public class Scoreboard { // hearts can't hold teams, so we treat them differently if (objective.getType() == 1) { for (Score score : objective.getScores().values()) { - if (score.getUpdateType() == NOTHING) { - continue; - } + boolean update = score.shouldUpdate(); - boolean update = score.getUpdateType() == UPDATE; if (update) { - score.update(); + score.update(objective.getObjectiveName()); } - if (score.getUpdateType() == ADD || update) { + if (score.getUpdateType() != REMOVE && update) { addScores.add(score.getCachedInfo()); } - if (score.getUpdateType() == REMOVE || update) { + if (score.getUpdateType() != ADD && update) { removeScores.add(score.getCachedInfo()); } } continue; } - boolean globalUpdate = objective.getUpdateType() == UPDATE; - boolean globalAdd = objective.getUpdateType() == ADD; - boolean globalRemove = objective.getUpdateType() == REMOVE; + boolean objectiveUpdate = objective.getUpdateType() == UPDATE; + boolean objectiveAdd = objective.getUpdateType() == ADD; + boolean objectiveRemove = objective.getUpdateType() == REMOVE; for (Score score : objective.getScores().values()) { Team team = score.getTeam(); - boolean add = globalAdd || globalUpdate; - boolean remove = globalRemove; - boolean teamChanged = false; + boolean add = objectiveAdd || objectiveUpdate; + boolean remove = false; if (team != null) { if (team.getUpdateType() == REMOVE || !team.hasEntity(score.getName())) { score.setTeam(null); - teamChanged = true; + add = true; + remove = true; } - - teamChanged |= team.getUpdateType() == UPDATE; - - add |= team.getUpdateType() == ADD || team.getUpdateType() == UPDATE; - remove |= team.getUpdateType() != NOTHING; } - add |= score.getUpdateType() == ADD || score.getUpdateType() == UPDATE; - remove |= score.getUpdateType() == REMOVE || score.getUpdateType() == UPDATE; + add |= score.shouldUpdate(); + remove |= score.shouldUpdate(); - if (score.getUpdateType() == REMOVE || globalRemove) { + if (score.getUpdateType() == REMOVE || objectiveRemove) { add = false; } - if (score.getUpdateType() == ADD) { + if (score.getUpdateType() == ADD || objectiveRemove) { remove = false; } - if (score.getUpdateType() == ADD || score.getUpdateType() == UPDATE || teamChanged) { - score.update(); + if (score.shouldUpdate()) { + score.update(objective.getObjectiveName()); } if (add) { addScores.add(score.getCachedInfo()); } + if (remove) { removeScores.add(score.getCachedInfo()); } @@ -200,17 +209,17 @@ public class Scoreboard { score.setUpdateType(NOTHING); } - if (globalRemove || globalUpdate) { + if (objectiveRemove) { + removedObjectives.add(objective); + } + + if (objectiveUpdate) { RemoveObjectivePacket removeObjectivePacket = new RemoveObjectivePacket(); removeObjectivePacket.setObjectiveId(objective.getObjectiveName()); session.sendUpstreamPacket(removeObjectivePacket); - if (globalRemove) { - objectives.remove(objective.getObjectiveName()); // now we can deregister - objective.removed(); - } } - if ((globalAdd || globalUpdate) && !globalRemove) { + if ((objectiveAdd || objectiveUpdate) && !objectiveRemove) { SetDisplayObjectivePacket displayObjectivePacket = new SetDisplayObjectivePacket(); displayObjectivePacket.setObjectiveId(objective.getObjectiveName()); displayObjectivePacket.setDisplayName(objective.getDisplayName()); @@ -223,6 +232,20 @@ public class Scoreboard { objective.setUpdateType(NOTHING); } + Iterator teamIterator = teams.values().iterator(); + while (teamIterator.hasNext()) { + Team current = teamIterator.next(); + + switch (current.getUpdateType()) { + case ADD: + case UPDATE: + current.markUpdated(); + break; + case REMOVE: + teamIterator.remove(); + } + } + if (!removeScores.isEmpty()) { SetScorePacket setScorePacket = new SetScorePacket(); setScorePacket.setAction(SetScorePacket.Action.REMOVE); @@ -237,37 +260,27 @@ public class Scoreboard { session.sendUpstreamPacket(setScorePacket); } - lastScoreCount = addScores.size(); + // prevents crashes in some cases + for (Objective objective : removedObjectives) { + despawnObjective(objective); + } + + lastAddScoreCount = addScores.size(); + lastRemoveScoreCount = removeScores.size(); } public void despawnObjective(Objective objective) { + objectives.remove(objective.getObjectiveName()); + objective.removed(); + RemoveObjectivePacket removeObjectivePacket = new RemoveObjectivePacket(); removeObjectivePacket.setObjectiveId(objective.getObjectiveName()); session.sendUpstreamPacket(removeObjectivePacket); - objectives.remove(objective.getDisplayName()); - - List toRemove = new ArrayList<>(); - for (String identifier : objective.getScores().keySet()) { - Score score = objective.getScores().get(identifier); - toRemove.add(new ScoreInfo( - score.getId(), score.getObjective().getObjectiveName(), - 0, "" - )); - } - - objective.removed(); - - if (!toRemove.isEmpty()) { - SetScorePacket setScorePacket = new SetScorePacket(); - setScorePacket.setAction(SetScorePacket.Action.REMOVE); - setScorePacket.setInfos(toRemove); - session.sendUpstreamPacket(setScorePacket); - } } public Team getTeamFor(String entity) { for (Team team : teams.values()) { - if (team.getEntities().contains(entity)) { + if (team.hasEntity(entity)) { return team; } } diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java b/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java index a073e2e9..377a15f1 100644 --- a/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java +++ b/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java @@ -28,6 +28,7 @@ package org.geysermc.connector.scoreboard; import com.github.steveice10.mc.protocol.data.game.scoreboard.NameTagVisibility; import com.github.steveice10.mc.protocol.data.game.scoreboard.TeamColor; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; @@ -36,62 +37,90 @@ import java.util.ArrayList; import java.util.List; import java.util.Set; -@Getter @Setter +@Getter @Accessors(chain = true) -public class Team { +public final class Team { private final Scoreboard scoreboard; private final String id; - private UpdateType updateType = UpdateType.ADD; - private String name; + @Getter(AccessLevel.NONE) + private final Set entities; + @Setter private NameTagVisibility nameTagVisibility; + @Setter private TeamColor color; - private NameTagVisibility nameTagVisibility; - private String prefix; - private TeamColor color; - private String suffix; - private Set entities = new ObjectOpenHashSet<>(); + private TeamData currentData; + private TeamData cachedData; + + private boolean updating; public Team(Scoreboard scoreboard, String id) { this.scoreboard = scoreboard; this.id = id; + currentData = new TeamData(); + entities = new ObjectOpenHashSet<>(); } - public void addEntities(String... names) { - List added = new ArrayList<>(); - for (String name : names) { - if (entities.add(name)) { - added.add(name); - } + private void checkAddedEntities(List added) { + if (added.size() == 0) { + return; } - setUpdateType(UpdateType.UPDATE); + // we don't have to change the updateType, + // because the scores itself need updating, not the team for (Objective objective : scoreboard.getObjectives().values()) { - for (Score score : objective.getScores().values()) { - if (added.contains(score.getName())) { + for (String addedEntity : added) { + Score score = objective.getScores().get(addedEntity); + if (score != null) { score.setTeam(this); } } } } + public Team addEntities(String... names) { + List added = new ArrayList<>(); + for (String name : names) { + if (entities.add(name)) { + added.add(name); + } + } + checkAddedEntities(added); + return this; + } + + public Team addEntities(Set names) { + List added = new ArrayList<>(); + for (String name : names) { + if (entities.add(name)) { + added.add(name); + } + } + checkAddedEntities(added); + return this; + } + public void removeEntities(String... names) { for (String name : names) { entities.remove(name); } - setUpdateType(UpdateType.UPDATE); } public boolean hasEntity(String name) { return entities.contains(name); } + public Team setName(String name) { + currentData.name = name; + return this; + } + public Team setPrefix(String prefix) { // replace "null" to an empty string, // we do this here to improve the performance of Score#getDisplayName if (prefix.length() == 4 && "null".equals(prefix)) { - this.prefix = ""; + currentData.prefix = ""; return this; } - this.prefix = prefix; + currentData.prefix = prefix; return this; } @@ -99,15 +128,92 @@ public class Team { // replace "null" to an empty string, // we do this here to improve the performance of Score#getDisplayName if (suffix.length() == 4 && "null".equals(suffix)) { - this.suffix = ""; + currentData.suffix = ""; return this; } - this.suffix = suffix; + currentData.suffix = suffix; return this; } + public String getDisplayName(String score) { + return cachedData != null ? + cachedData.getDisplayName(score) : + currentData.getDisplayName(score); + } + + public void markUpdated() { + updating = false; + } + + public boolean shouldUpdate() { + return updating || cachedData == null || currentData.updateTime > cachedData.updateTime; + } + + public void prepareUpdate() { + if (updating) { + return; + } + updating = true; + + if (cachedData == null) { + cachedData = new TeamData(); + cachedData.updateType = currentData.updateType != UpdateType.REMOVE ? UpdateType.ADD : UpdateType.REMOVE; + } else { + cachedData.updateType = currentData.updateType; + } + + cachedData.updateTime = currentData.updateTime; + cachedData.name = currentData.name; + cachedData.prefix = currentData.prefix; + cachedData.suffix = currentData.suffix; + } + + public UpdateType getUpdateType() { + return cachedData != null ? cachedData.updateType : currentData.updateType; + } + + public Team setUpdateType(UpdateType updateType) { + if (updateType != UpdateType.NOTHING) { + currentData.updateTime = System.currentTimeMillis(); + } + currentData.updateType = updateType; + return this; + } + + public boolean isVisibleFor(String entity) { + switch (nameTagVisibility) { + case HIDE_FOR_OTHER_TEAMS: + return hasEntity(entity); + case HIDE_FOR_OWN_TEAM: + return !hasEntity(entity); + case ALWAYS: + return true; + case NEVER: + return false; + } + return true; + } + @Override public int hashCode() { return id.hashCode(); } + + @Getter + public static final class TeamData { + protected UpdateType updateType; + protected long updateTime; + + protected String name; + protected String prefix; + protected String suffix; + + protected TeamData() { + updateType = UpdateType.ADD; + } + + public String getDisplayName(String score) { + return prefix + score + suffix; + } + } } From 109922f796c9f7c0f136e8a536357215a7afcf0c Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Mon, 9 Nov 2020 14:26:29 -0500 Subject: [PATCH 105/205] Update MCProtocolLib to fix datapacks that send empty result recipes (#1522) Example: https://www.planetminecraft.com/data-pack/true-survival-a-hardcore-minecraft-experience/ --- connector/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/connector/pom.xml b/connector/pom.xml index edf14abb..ee3164e1 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -109,9 +109,9 @@ compile
- com.github.GeyserMC + com.github.steveice10 mcprotocollib - e4181064d1 + 6ac79c14d6 compile From 4237503d6d0ae7c942f74f505f7706eba9996626 Mon Sep 17 00:00:00 2001 From: David Choo Date: Tue, 10 Nov 2020 12:05:16 -0500 Subject: [PATCH 106/205] Fix laying crash (#1510) * Fix crash with GSit lay and use Java bed position data * Fix GSit's lay position * Move Bed Position metadata to the right class * Actually fix lay for PosePlugin * Revert "Actually fix lay for PosePlugin" This reverts commit 3f21261162c439b3b167f057e75c2884193b75f7. Co-authored-by: DoctorMacc --- .../org/geysermc/connector/entity/Entity.java | 12 ---------- .../connector/entity/LivingEntity.java | 14 ++++++++++++ .../connector/entity/PlayerEntity.java | 13 +++++++++++ .../living/merchant/VillagerEntity.java | 7 +++--- .../spawn/JavaSpawnPlayerTranslator.java | 22 ++++++++++++------- 5 files changed, 45 insertions(+), 23 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/entity/Entity.java b/connector/src/main/java/org/geysermc/connector/entity/Entity.java index 2dfb0c04..e579f4e9 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/Entity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/Entity.java @@ -339,18 +339,6 @@ public class Entity { metadata.getFlags().setFlag(EntityFlag.SLEEPING, true); // Has to be a byte or it does not work metadata.put(EntityData.PLAYER_FLAGS, (byte) 2); - if (entityId == session.getPlayerEntity().getEntityId()) { - Vector3i lastInteractionPos = session.getLastInteractionPosition(); - metadata.put(EntityData.BED_POSITION, lastInteractionPos); - if (session.getConnector().getConfig().isCacheChunks()) { - int bed = session.getConnector().getWorldManager().getBlockAt(session, lastInteractionPos.getX(), - lastInteractionPos.getY(), lastInteractionPos.getZ()); - // Bed has to be updated, or else player is floating in the air - ChunkUtils.updateBlock(session, bed, lastInteractionPos); - } - } else { - metadata.put(EntityData.BED_POSITION, Vector3i.from(position.getFloorX(), position.getFloorY() - 2, position.getFloorZ())); - } metadata.put(EntityData.BOUNDING_BOX_WIDTH, 0.2f); metadata.put(EntityData.BOUNDING_BOX_HEIGHT, 0.2f); } else if (metadata.getFlags().getFlag(EntityFlag.SLEEPING)) { diff --git a/connector/src/main/java/org/geysermc/connector/entity/LivingEntity.java b/connector/src/main/java/org/geysermc/connector/entity/LivingEntity.java index ae9eaee5..345c19de 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/LivingEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/LivingEntity.java @@ -26,7 +26,9 @@ package org.geysermc.connector.entity; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.AttributeData; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; @@ -42,6 +44,7 @@ import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.utils.AttributeUtils; +import org.geysermc.connector.utils.ChunkUtils; import java.util.ArrayList; import java.util.List; @@ -84,6 +87,17 @@ public class LivingEntity extends Entity { case 10: metadata.put(EntityData.EFFECT_AMBIENT, (byte) ((boolean) entityMetadata.getValue() ? 1 : 0)); break; + case 13: // Bed Position + Position bedPosition = (Position) entityMetadata.getValue(); + if (bedPosition != null) { + metadata.put(EntityData.BED_POSITION, Vector3i.from(bedPosition.getX(), bedPosition.getY(), bedPosition.getZ())); + if (session.getConnector().getConfig().isCacheChunks()) { + int bed = session.getConnector().getWorldManager().getBlockAt(session, bedPosition); + // Bed has to be updated, or else player is floating in the air + ChunkUtils.updateBlock(session, bed, bedPosition); + } + } + break; } super.updateBedrockMetadata(entityMetadata, session); diff --git a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java index bfadd83c..8eeae473 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java @@ -30,10 +30,12 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadat import com.github.steveice10.mc.protocol.data.message.TextMessage; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.AttributeData; import com.nukkitx.protocol.bedrock.data.PlayerPermission; import com.nukkitx.protocol.bedrock.data.command.CommandPermission; import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.data.entity.EntityLinkData; import com.nukkitx.protocol.bedrock.packet.AddPlayerPacket; import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket; @@ -167,6 +169,17 @@ public class PlayerEntity extends LivingEntity { movePlayerPacket.setRotation(getBedrockRotation()); movePlayerPacket.setOnGround(isOnGround); movePlayerPacket.setMode(MovePlayerPacket.Mode.NORMAL); + // If the player is moved while sleeping, we have to adjust their y, so it appears + // correctly on Bedrock. This fixes GSit's lay. + if (metadata.getFlags().getFlag(EntityFlag.SLEEPING)) { + Vector3i bedPosition = metadata.getPos(EntityData.BED_POSITION); + if (bedPosition != null && (bedPosition.getY() == 0 || bedPosition.distanceSquared(position.toInt()) > 4)) { + // Force the player movement by using a teleport + movePlayerPacket.setPosition(Vector3f.from(position.getX(), position.getY() - entityType.getOffset() + 0.2f, position.getZ())); + movePlayerPacket.setMode(MovePlayerPacket.Mode.TELEPORT); + movePlayerPacket.setTeleportationCause(MovePlayerPacket.TeleportationCause.UNKNOWN); + } + } session.sendUpstreamPacket(movePlayerPacket); if (leftParrot != null) { leftParrot.moveRelative(session, relX, relY, relZ, rotation, true); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java index 7ada302c..98d5a631 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java @@ -29,6 +29,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadat import com.github.steveice10.mc.protocol.data.game.entity.metadata.VillagerData; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.data.entity.EntityFlags; @@ -99,9 +100,9 @@ public class VillagerEntity extends AbstractMerchantEntity { int bedId = 0; float bedPositionSubtractorW = 0; float bedPositionSubtractorN = 0; - if (session.getConnector().getConfig().isCacheChunks()) { - Position bedLocation = new Position((int) position.getFloorX(), (int) position.getFloorY(), (int) position.getFloorZ()); - bedId = session.getConnector().getWorldManager().getBlockAt(session, bedLocation); + Vector3i bedPosition = metadata.getPos(EntityData.BED_POSITION); + if (session.getConnector().getConfig().isCacheChunks() && bedPosition != null) { + bedId = session.getConnector().getWorldManager().getBlockAt(session, bedPosition); } String bedRotationZ = BlockTranslator.getJavaIdBlockMap().inverse().get(bedId); setRotation(rotation); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnPlayerTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnPlayerTranslator.java index 47beca8a..99a6b049 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnPlayerTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnPlayerTranslator.java @@ -43,15 +43,21 @@ public class JavaSpawnPlayerTranslator extends PacketTranslator Date: Wed, 11 Nov 2020 18:13:13 +0000 Subject: [PATCH 107/205] Fix even more entity metadata flags (#1483) * Fix even more entity metadata flags * Add comment explaining magic value * Fix horse flags and add more information * Add more information about the Horse eating particles --- .../connector/entity/living/BatEntity.java | 48 ++++++++++++++++++ .../entity/living/SnowGolemEntity.java | 49 ++++++++++++++++++ .../entity/living/animal/FoxEntity.java | 1 + .../entity/living/animal/MooshroomEntity.java | 47 +++++++++++++++++ .../entity/living/animal/RabbitEntity.java | 9 ++++ .../entity/living/animal/TurtleEntity.java | 49 ++++++++++++++++++ .../animal/horse/AbstractHorseEntity.java | 28 +++++++++++ .../entity/living/monster/VexEntity.java | 50 +++++++++++++++++++ .../connector/entity/type/EntityType.java | 10 ++-- .../translators/item/ItemRegistry.java | 7 +++ 10 files changed, 293 insertions(+), 5 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/entity/living/BatEntity.java create mode 100644 connector/src/main/java/org/geysermc/connector/entity/living/SnowGolemEntity.java create mode 100644 connector/src/main/java/org/geysermc/connector/entity/living/animal/MooshroomEntity.java create mode 100644 connector/src/main/java/org/geysermc/connector/entity/living/animal/TurtleEntity.java create mode 100644 connector/src/main/java/org/geysermc/connector/entity/living/monster/VexEntity.java diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/BatEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/BatEntity.java new file mode 100644 index 00000000..b7b7534c --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/BatEntity.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.entity.living; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class BatEntity extends AmbientEntity { + + public BatEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 15) { + byte xd = (byte) entityMetadata.getValue(); + metadata.getFlags().setFlag(EntityFlag.RESTING, (xd & 0x01) == 0x01); + } + super.updateBedrockMetadata(entityMetadata, session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/SnowGolemEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/SnowGolemEntity.java new file mode 100644 index 00000000..2f75e645 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/SnowGolemEntity.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.entity.living; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class SnowGolemEntity extends GolemEntity { + + public SnowGolemEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 15) { + byte xd = (byte) entityMetadata.getValue(); + // Handle the visibility of the pumpkin + metadata.getFlags().setFlag(EntityFlag.SHEARED, (xd & 0x10) != 0x10); + } + super.updateBedrockMetadata(entityMetadata, session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/FoxEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/FoxEntity.java index 88c30cbf..1d924994 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/FoxEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/FoxEntity.java @@ -47,6 +47,7 @@ public class FoxEntity extends AnimalEntity { byte xd = (byte) entityMetadata.getValue(); metadata.getFlags().setFlag(EntityFlag.SITTING, (xd & 0x01) == 0x01); metadata.getFlags().setFlag(EntityFlag.SNEAKING, (xd & 0x04) == 0x04); + metadata.getFlags().setFlag(EntityFlag.INTERESTED, (xd & 0x08) == 0x08); metadata.getFlags().setFlag(EntityFlag.SLEEPING, (xd & 0x20) == 0x20); } super.updateBedrockMetadata(entityMetadata, session); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/MooshroomEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/MooshroomEntity.java new file mode 100644 index 00000000..69fb55fb --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/MooshroomEntity.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.entity.living.animal; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class MooshroomEntity extends AnimalEntity { + + public MooshroomEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 16) { + metadata.put(EntityData.VARIANT, entityMetadata.getValue().equals("brown") ? 1 : 0); + } + super.updateBedrockMetadata(entityMetadata, session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/RabbitEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/RabbitEntity.java index 0b61713a..79202792 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/RabbitEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/RabbitEntity.java @@ -48,6 +48,15 @@ public class RabbitEntity extends AnimalEntity { metadata.put(EntityData.SCALE, .35f); metadata.getFlags().setFlag(EntityFlag.BABY, true); } + } else if (entityMetadata.getId() == 16) { + int variant = (int) entityMetadata.getValue(); + + // Change the killer bunny to display as white since it only exists on Java Edition + if (variant == 99) { + variant = 1; + } + + metadata.put(EntityData.VARIANT, variant); } } } \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/TurtleEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/TurtleEntity.java new file mode 100644 index 00000000..555e2268 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/TurtleEntity.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.entity.living.animal; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class TurtleEntity extends AnimalEntity { + + public TurtleEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 17) { + metadata.getFlags().setFlag(EntityFlag.IS_PREGNANT, (boolean) entityMetadata.getValue()); + } else if (entityMetadata.getId() == 18) { + metadata.getFlags().setFlag(EntityFlag.LAYING_EGG, (boolean) entityMetadata.getValue()); + } + super.updateBedrockMetadata(entityMetadata, session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/AbstractHorseEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/AbstractHorseEntity.java index e08f9adf..cf9f84b4 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/AbstractHorseEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/AbstractHorseEntity.java @@ -27,10 +27,14 @@ package org.geysermc.connector.entity.living.animal.horse; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; import org.geysermc.connector.entity.living.animal.AnimalEntity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.item.ItemRegistry; public class AbstractHorseEntity extends AnimalEntity { @@ -47,6 +51,30 @@ public class AbstractHorseEntity extends AnimalEntity { metadata.getFlags().setFlag(EntityFlag.SADDLED, (xd & 0x04) == 0x04); metadata.getFlags().setFlag(EntityFlag.EATING, (xd & 0x10) == 0x10); metadata.getFlags().setFlag(EntityFlag.STANDING, (xd & 0x20) == 0x20); + + // HorseFlags + // Bred 0x10 + // Eating 0x20 + // Open mouth 0x80 + int horseFlags = 0x0; + horseFlags = (xd & 0x40) == 0x40 ? horseFlags | 0x80 : horseFlags; + + // Only set eating when we don't have mouth open so a player interaction doesn't trigger the eating animation + horseFlags = (xd & 0x10) == 0x10 && (xd & 0x40) != 0x40 ? horseFlags | 0x20 : horseFlags; + + // Set the flags into the display item + metadata.put(EntityData.DISPLAY_ITEM, horseFlags); + + // Send the eating particles + // We use the wheat metadata as static particles since Java + // doesn't send over what item was used to feed the horse + if ((xd & 0x40) == 0x40) { + EntityEventPacket entityEventPacket = new EntityEventPacket(); + entityEventPacket.setRuntimeEntityId(geyserId); + entityEventPacket.setType(EntityEventType.EATING_ITEM); + entityEventPacket.setData(ItemRegistry.WHEAT.getBedrockId() << 16); + session.sendUpstreamPacket(entityEventPacket); + } } // Needed to control horses diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/VexEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/VexEntity.java new file mode 100644 index 00000000..70e41329 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/VexEntity.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.entity.living.monster; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class VexEntity extends MonsterEntity { + + public VexEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 15) { + byte xd = (byte) entityMetadata.getValue(); + // Set the target to the player to force the attack animation + // even if the player isn't the target as we dont get the target on Java + metadata.put(EntityData.TARGET_EID, (xd & 0x01) == 0x01 ? session.getPlayerEntity().getGeyserId() : 0); + } + super.updateBedrockMetadata(entityMetadata, session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java index 7557bd04..f023ca10 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java +++ b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java @@ -47,12 +47,12 @@ public enum EntityType { SHEEP(SheepEntity.class, 13, 1.3f, 0.9f), WOLF(WolfEntity.class, 14, 0.85f, 0.6f), VILLAGER(VillagerEntity.class, 15, 1.8f, 0.6f, 0.6f, 1.62f, "minecraft:villager_v2"), - MOOSHROOM(AnimalEntity.class, 16, 1.4f, 0.9f), + MOOSHROOM(MooshroomEntity.class, 16, 1.4f, 0.9f), SQUID(SquidEntity.class, 17, 0.8f), RABBIT(RabbitEntity.class, 18, 0.5f, 0.4f), - BAT(AmbientEntity.class, 19, 0.9f, 0.5f), + BAT(BatEntity.class, 19, 0.9f, 0.5f), IRON_GOLEM(GolemEntity.class, 20, 2.7f, 1.4f), - SNOW_GOLEM(GolemEntity.class, 21, 1.9f, 0.7f), + SNOW_GOLEM(SnowGolemEntity.class, 21, 1.9f, 0.7f), OCELOT(OcelotEntity.class, 22, 0.35f, 0.3f), HORSE(HorseEntity.class, 23, 1.6f, 1.3965f), DONKEY(ChestedHorseEntity.class, 24, 1.6f, 1.3965f), @@ -109,7 +109,7 @@ public enum EntityType { END_CRYSTAL(EnderCrystalEntity.class, 71, 2.0f, 2.0f, 2.0f, 0f, "minecraft:ender_crystal"), FIREWORK_ROCKET(FireworkEntity.class, 72, 0.25f, 0.25f, 0.25f, 0f, "minecraft:fireworks_rocket"), TRIDENT(TridentEntity.class, 73, 0f, 0f, 0f, 0f, "minecraft:thrown_trident"), - TURTLE(AnimalEntity.class, 74, 0.4f, 1.2f), + TURTLE(TurtleEntity.class, 74, 0.4f, 1.2f), CAT(CatEntity.class, 75, 0.35f, 0.3f), SHULKER_BULLET(Entity.class, 76, 0.3125f), FISHING_BOBBER(FishingHookEntity.class, 77, 0f, 0f, 0f, 0f, "minecraft:fishing_hook"), @@ -141,7 +141,7 @@ public enum EntityType { LLAMA_SPIT(Entity.class, 102, 0.25f), EVOKER_FANGS(Entity.class, 103, 0.8f, 0.5f, 0.5f, 0f, "minecraft:evocation_fang"), EVOKER(SpellcasterIllagerEntity.class, 104, 1.95f, 0.6f, 0.6f, 0f, "minecraft:evocation_illager"), - VEX(MonsterEntity.class, 105, 0.8f, 0.4f), + VEX(VexEntity.class, 105, 0.8f, 0.4f), ICE_BOMB(Entity.class, 106, 0f), BALLOON(Entity.class, 107, 0f), //TODO PUFFERFISH(PufferFishEntity.class, 108, 0.7f, 0.7f), diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java index b52d27ff..8370ba8e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java @@ -75,6 +75,10 @@ public class ItemRegistry { * Shield item entry, used in Entity.java and LivingEntity.java */ public static ItemEntry SHIELD; + /** + * Wheat item entry, used in AbstractHorseEntity.java + */ + public static ItemEntry WHEAT; public static int BARRIER_INDEX = 0; @@ -157,6 +161,9 @@ public class ItemRegistry { case "minecraft:bucket": BUCKET = ITEM_ENTRIES.get(itemIndex); break; + case "minecraft:wheat": + WHEAT = ITEM_ENTRIES.get(itemIndex); + break; default: break; } From e748240a02c9eb7062fb114f3dcd285f4efb47fe Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Wed, 11 Nov 2020 19:28:45 -0500 Subject: [PATCH 108/205] Add more interactive tags (mobile buttons) (#1443) * Add more interactive tags (mobile buttons) This expands our support for showing the interactive tags on touchscreen and console setups. This is not complete - specifically, the food compatibility of creatures needs to be expanded upon (I will work on this later and does not stop this PR from being mergable). This also includes: - Creepers who are ignited with flint and steel now show up properly - Zombie villagers now shake properly when converting and show their region outfits * Add more food choices and add more panda entity metadata * Re-add eating flag * Remove debug line * Refactor dimension usage, finish interactive tag usage, bees * Print statements... ._. * Don't make eating item packet data a non-constant * Move BAMBOO to ItemRegistry * Add missing break * Make changes * Minor final changes --- .../standalone/GeyserStandaloneBootstrap.java | 4 +- .../geysermc/connector/GeyserConnector.java | 3 +- .../org/geysermc/connector/entity/Entity.java | 3 - .../entity/living/animal/BeeEntity.java | 18 +- .../entity/living/animal/FoxEntity.java | 2 +- .../entity/living/animal/HoglinEntity.java | 50 +++ .../entity/living/animal/PandaEntity.java | 54 ++- .../living/animal/horse/HorseEntity.java | 3 +- .../living/animal/tameable/CatEntity.java | 13 +- .../animal/tameable/TameableEntity.java | 24 +- .../living/merchant/VillagerEntity.java | 15 +- .../living/monster/BasePiglinEntity.java | 16 +- .../entity/living/monster/CreeperEntity.java | 13 +- .../entity/living/monster/PiglinEntity.java | 2 +- .../living/monster/ZombieVillagerEntity.java | 56 +++ .../connector/entity/type/EntityType.java | 4 +- .../network/session/GeyserSession.java | 9 +- .../player/BedrockInteractTranslator.java | 381 +++++++++++++++--- .../translators/item/ItemRegistry.java | 7 + .../java/JavaJoinGameTranslator.java | 4 +- .../java/JavaRespawnTranslator.java | 4 +- .../entity/JavaEntityAttachTranslator.java | 2 +- .../entity/JavaEntityStatusTranslator.java | 20 +- .../java/world/JavaMapDataTranslator.java | 2 +- .../world/JavaSpawnParticleTranslator.java | 2 +- .../world/JavaSpawnPositionTranslator.java | 2 +- .../connector/utils/DimensionUtils.java | 29 +- .../geysermc/connector/utils/LocaleUtils.java | 11 +- 28 files changed, 643 insertions(+), 110 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/entity/living/animal/HoglinEntity.java create mode 100644 connector/src/main/java/org/geysermc/connector/entity/living/monster/ZombieVillagerEntity.java diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java index f58a997e..f4dfd454 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java @@ -103,11 +103,11 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { case "--config": case "-c": if (i >= args.length - 1) { - System.err.println(MessageFormat.format(LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.confignotspecified"), "-c")); + System.err.println(MessageFormat.format(LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.config_not_specified"), "-c")); return; } configFilenameOpt = args[i+1]; i++; - System.out.println(MessageFormat.format(LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.configspecified"), configFilenameOpt)); + System.out.println(MessageFormat.format(LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.config_specified"), configFilenameOpt)); break; case "--help": case "-h": diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 1d535f54..2497166c 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -179,8 +179,7 @@ public class GeyserConnector { remoteServer = new RemoteServer(config.getRemote().getAddress(), remotePort); authType = AuthType.getByName(config.getRemote().getAuthType()); - if (config.isAboveBedrockNetherBuilding()) - DimensionUtils.changeBedrockNetherId(); // Apply End dimension ID workaround to Nether + DimensionUtils.changeBedrockNetherId(config.isAboveBedrockNetherBuilding()); // Apply End dimension ID workaround to Nether // https://github.com/GeyserMC/Geyser/issues/957 RakNetConstants.MAXIMUM_MTU_SIZE = (short) config.getMtu(); diff --git a/connector/src/main/java/org/geysermc/connector/entity/Entity.java b/connector/src/main/java/org/geysermc/connector/entity/Entity.java index e579f4e9..20cd2f76 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/Entity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/Entity.java @@ -67,8 +67,6 @@ public class Entity { protected long entityId; protected long geyserId; - protected String dimension; - protected Vector3f position; protected Vector3f motion; @@ -100,7 +98,6 @@ public class Entity { this.rotation = rotation; this.valid = false; - this.dimension = "minecraft:overworld"; setPosition(position); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/BeeEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/BeeEntity.java index c46f00fe..ee17e2a2 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/BeeEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/BeeEntity.java @@ -27,7 +27,10 @@ package org.geysermc.connector.entity.living.animal; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; @@ -41,10 +44,23 @@ public class BeeEntity extends AnimalEntity { public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { if (entityMetadata.getId() == 16) { byte xd = (byte) entityMetadata.getValue(); - metadata.getFlags().setFlag(EntityFlag.ANGRY, (xd & 0x02) == 0x02); + // Bee is performing sting attack; trigger animation + if ((xd & 0x02) == 0x02) { + EntityEventPacket packet = new EntityEventPacket(); + packet.setRuntimeEntityId(geyserId); + packet.setType(EntityEventType.ATTACK_START); + packet.setData(0); + session.sendUpstreamPacket(packet); + } + // If the bee has stung + metadata.put(EntityData.MARK_VARIANT, (xd & 0x04) == 0x04 ? 1 : 0); // If the bee has nectar or not metadata.getFlags().setFlag(EntityFlag.POWERED, (xd & 0x08) == 0x08); } + if (entityMetadata.getId() == 17) { + // Converting "anger time" to a boolean + metadata.getFlags().setFlag(EntityFlag.ANGRY, (int) entityMetadata.getValue() > 0); + } super.updateBedrockMetadata(entityMetadata, session); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/FoxEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/FoxEntity.java index 1d924994..bbc2d7de 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/FoxEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/FoxEntity.java @@ -41,7 +41,7 @@ public class FoxEntity extends AnimalEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { if (entityMetadata.getId() == 16) { - metadata.put(EntityData.VARIANT, (int) entityMetadata.getValue()); + metadata.put(EntityData.VARIANT, entityMetadata.getValue()); } if (entityMetadata.getId() == 17) { byte xd = (byte) entityMetadata.getValue(); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/HoglinEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/HoglinEntity.java new file mode 100644 index 00000000..3fd29172 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/HoglinEntity.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.entity.living.animal; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.utils.DimensionUtils; + +public class HoglinEntity extends AnimalEntity { + + public HoglinEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 16) { + // Immune to zombification? + // Apply shaking effect if not in the nether and zombification is possible + metadata.getFlags().setFlag(EntityFlag.SHAKING, !((boolean) entityMetadata.getValue()) && !session.getDimension().equals(DimensionUtils.NETHER)); + } + super.updateBedrockMetadata(entityMetadata, session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/PandaEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/PandaEntity.java index 7e555122..ed3ed80b 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/PandaEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/PandaEntity.java @@ -27,23 +27,75 @@ package org.geysermc.connector.entity.living.animal; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.item.ItemRegistry; public class PandaEntity extends AnimalEntity { + private int mainGene; + private int hiddenGene; + public PandaEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); } @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 18) { + metadata.getFlags().setFlag(EntityFlag.EATING, (int) entityMetadata.getValue() > 0); + metadata.put(EntityData.EATING_COUNTER, entityMetadata.getValue()); + if ((int) entityMetadata.getValue() != 0) { + // Particles and sound + EntityEventPacket packet = new EntityEventPacket(); + packet.setRuntimeEntityId(geyserId); + packet.setType(EntityEventType.EATING_ITEM); + packet.setData(ItemRegistry.BAMBOO.getBedrockId() << 16); + session.sendUpstreamPacket(packet); + } + } + if (entityMetadata.getId() == 19) { + mainGene = (int) (byte) entityMetadata.getValue(); + updateAppearance(); + } + if (entityMetadata.getId() == 20) { + hiddenGene = (int) (byte) entityMetadata.getValue(); + updateAppearance(); + } if (entityMetadata.getId() == 21) { byte xd = (byte) entityMetadata.getValue(); metadata.getFlags().setFlag(EntityFlag.SNEEZING, (xd & 0x02) == 0x02); - metadata.getFlags().setFlag(EntityFlag.EATING, (xd & 0x04) == 0x04); + metadata.getFlags().setFlag(EntityFlag.ROLLING, (xd & 0x04) == 0x04); + metadata.getFlags().setFlag(EntityFlag.SITTING, (xd & 0x08) == 0x08); + // Required to put these both for sitting to actually show + metadata.put(EntityData.SITTING_AMOUNT, (xd & 0x08) == 0x08 ? 1f : 0f); + metadata.put(EntityData.SITTING_AMOUNT_PREVIOUS, (xd & 0x08) == 0x08 ? 1f : 0f); + metadata.getFlags().setFlag(EntityFlag.LAYING_DOWN, (xd & 0x10) == 0x10); } super.updateBedrockMetadata(entityMetadata, session); } + + /** + * Update the panda's appearance, and take into consideration the recessive brown and weak traits that only show up + * when both main and hidden genes match + */ + private void updateAppearance() { + if (mainGene == 4 || mainGene == 5) { + // Main gene is a recessive trait + if (mainGene == hiddenGene) { + // Main and hidden genes match; this is what the panda looks like. + metadata.put(EntityData.VARIANT, mainGene); + } else { + // Genes have no effect on appearance + metadata.put(EntityData.VARIANT, 0); + } + } else { + // No need to worry about hidden gene + metadata.put(EntityData.VARIANT, mainGene); + } + } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/HorseEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/HorseEntity.java index da3ff349..349da5e0 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/HorseEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/HorseEntity.java @@ -28,7 +28,6 @@ package org.geysermc.connector.entity.living.animal.horse; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; @@ -41,7 +40,7 @@ public class HorseEntity extends AbstractHorseEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { if (entityMetadata.getId() == 18) { - metadata.put(EntityData.VARIANT, (int) entityMetadata.getValue()); + metadata.put(EntityData.VARIANT, entityMetadata.getValue()); metadata.put(EntityData.MARK_VARIANT, (((int) entityMetadata.getValue()) >> 8) % 5); } super.updateBedrockMetadata(entityMetadata, session); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/CatEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/CatEntity.java index 067a360c..5c5de546 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/CatEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/CatEntity.java @@ -34,6 +34,8 @@ import org.geysermc.connector.network.session.GeyserSession; public class CatEntity extends TameableEntity { + private byte collarColor; + public CatEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); } @@ -45,6 +47,13 @@ public class CatEntity extends TameableEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + super.updateBedrockMetadata(entityMetadata, session); + if (entityMetadata.getId() == 16) { + // Update collar color if tamed + if (metadata.getFlags().getFlag(EntityFlag.TAMED)) { + metadata.put(EntityData.COLOR, collarColor); + } + } if (entityMetadata.getId() == 18) { // Different colors in Java and Bedrock for some reason int variantColor; @@ -67,11 +76,11 @@ public class CatEntity extends TameableEntity { metadata.put(EntityData.VARIANT, variantColor); } if (entityMetadata.getId() == 21) { + collarColor = (byte) (int) entityMetadata.getValue(); // Needed or else wild cats are a red color if (metadata.getFlags().getFlag(EntityFlag.TAMED)) { - metadata.put(EntityData.COLOR, (byte) (int) entityMetadata.getValue()); + metadata.put(EntityData.COLOR, collarColor); } } - super.updateBedrockMetadata(entityMetadata, session); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/TameableEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/TameableEntity.java index 2e8ab816..9e73ebe5 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/TameableEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/TameableEntity.java @@ -29,10 +29,13 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadat import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.living.animal.AnimalEntity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; +import java.util.UUID; + public class TameableEntity extends AnimalEntity { public TameableEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { @@ -46,11 +49,22 @@ public class TameableEntity extends AnimalEntity { metadata.getFlags().setFlag(EntityFlag.SITTING, (xd & 0x01) == 0x01); metadata.getFlags().setFlag(EntityFlag.ANGRY, (xd & 0x02) == 0x02); metadata.getFlags().setFlag(EntityFlag.TAMED, (xd & 0x04) == 0x04); - // Must be set for wolf collar color to work - // Extending it to all entities to prevent future bugs - if (metadata.getFlags().getFlag(EntityFlag.TAMED)) { - metadata.put(EntityData.OWNER_EID, session.getPlayerEntity().getGeyserId()); - } // Can't de-tame an entity so no resetting the owner ID + } + + // Note: Must be set for wolf collar color to work + if (entityMetadata.getId() == 17) { + if (entityMetadata.getValue() != null) { + // Owner UUID of entity + Entity entity = session.getEntityCache().getPlayerEntity((UUID) entityMetadata.getValue()); + // Used as both a check since the player isn't in the entity cache and a normal fallback + if (entity == null) { + entity = session.getPlayerEntity(); + } + // Translate to entity ID + metadata.put(EntityData.OWNER_EID, entity.getGeyserId()); + } else { + metadata.put(EntityData.OWNER_EID, 0L); // Reset + } } super.updateBedrockMetadata(entityMetadata, session); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java index 98d5a631..028d1831 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java @@ -26,27 +26,32 @@ package org.geysermc.connector.entity.living.merchant; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.VillagerData; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.VillagerData; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import com.nukkitx.protocol.bedrock.data.entity.EntityFlags; -import com.nukkitx.protocol.bedrock.packet.*; +import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket; import it.unimi.dsi.fastutil.ints.Int2IntMap; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.world.WorldManager; import org.geysermc.connector.network.translators.world.block.BlockTranslator; + import java.util.regex.Matcher; import java.util.regex.Pattern; public class VillagerEntity extends AbstractMerchantEntity { + /** + * A map of Java profession IDs to Bedrock IDs + */ private static final Int2IntMap VILLAGER_VARIANTS = new Int2IntOpenHashMap(); - private static final Int2IntMap VILLAGER_REGIONS = new Int2IntOpenHashMap(); + /** + * A map of all Java region IDs (plains, savanna...) to Bedrock + */ + public static final Int2IntMap VILLAGER_REGIONS = new Int2IntOpenHashMap(); static { // Java villager profession IDs -> Bedrock diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/BasePiglinEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/BasePiglinEntity.java index 830c7ea3..b83a2ca7 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/BasePiglinEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/BasePiglinEntity.java @@ -1,11 +1,25 @@ package org.geysermc.connector.entity.living.monster; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.utils.DimensionUtils; public class BasePiglinEntity extends MonsterEntity { public BasePiglinEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); } -} \ No newline at end of file + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 15) { + // Immune to zombification? + // Apply shaking effect if not in the nether and zombification is possible + metadata.getFlags().setFlag(EntityFlag.SHAKING, !((boolean) entityMetadata.getValue()) && !session.getDimension().equals(DimensionUtils.NETHER)); + } + super.updateBedrockMetadata(entityMetadata, session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/CreeperEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/CreeperEntity.java index 3c3a76bd..f4931861 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/CreeperEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/CreeperEntity.java @@ -33,6 +33,12 @@ import org.geysermc.connector.network.session.GeyserSession; public class CreeperEntity extends MonsterEntity { + /** + * Whether the creeper has been ignited and is using ID 17. + * In this instance we ignore ID 15 since it's sending us -1 which confuses poor Bedrock. + */ + private boolean ignitedByFlintAndSteel = false; + public CreeperEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); } @@ -40,13 +46,16 @@ public class CreeperEntity extends MonsterEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { if (entityMetadata.getId() == 15) { - metadata.getFlags().setFlag(EntityFlag.IGNITED, (int) entityMetadata.getValue() == 1); + if (!ignitedByFlintAndSteel) { + metadata.getFlags().setFlag(EntityFlag.IGNITED, (int) entityMetadata.getValue() == 1); + } } if (entityMetadata.getId() == 16) { metadata.getFlags().setFlag(EntityFlag.POWERED, (boolean) entityMetadata.getValue()); } if (entityMetadata.getId() == 17) { - metadata.getFlags().setFlag(EntityFlag.IGNITED, (boolean) entityMetadata.getValue()); + ignitedByFlintAndSteel = (boolean) entityMetadata.getValue(); + metadata.getFlags().setFlag(EntityFlag.IGNITED, ignitedByFlintAndSteel); } super.updateBedrockMetadata(entityMetadata, session); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/PiglinEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/PiglinEntity.java index 7b0d71e1..e0b443d3 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/PiglinEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/PiglinEntity.java @@ -41,7 +41,7 @@ public class PiglinEntity extends BasePiglinEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 15) { + if (entityMetadata.getId() == 16) { boolean isBaby = (boolean) entityMetadata.getValue(); if (isBaby) { metadata.put(EntityData.SCALE, .55f); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/ZombieVillagerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/ZombieVillagerEntity.java new file mode 100644 index 00000000..b8a62817 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/ZombieVillagerEntity.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.entity.living.monster; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.VillagerData; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.connector.entity.living.merchant.VillagerEntity; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class ZombieVillagerEntity extends ZombieEntity { + + public ZombieVillagerEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 18) { + metadata.getFlags().setFlag(EntityFlag.IS_TRANSFORMING, (boolean) entityMetadata.getValue()); + metadata.getFlags().setFlag(EntityFlag.SHAKING, (boolean) entityMetadata.getValue()); + } + if (entityMetadata.getId() == 19) { + VillagerData villagerData = (VillagerData) entityMetadata.getValue(); + // Region - only one used on Bedrock + metadata.put(EntityData.MARK_VARIANT, VillagerEntity.VILLAGER_REGIONS.get(villagerData.getType())); + } + super.updateBedrockMetadata(entityMetadata, session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java index f023ca10..05447760 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java +++ b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java @@ -77,7 +77,7 @@ public enum EntityType { GHAST(GhastEntity.class, 41, 4.0f), MAGMA_CUBE(MagmaCubeEntity.class, 42, 0.51f), BLAZE(BlazeEntity.class, 43, 1.8f, 0.6f), - ZOMBIE_VILLAGER(ZombieEntity.class, 44, 1.8f, 0.6f, 0.6f, 1.62f), + ZOMBIE_VILLAGER(ZombieVillagerEntity.class, 44, 1.8f, 0.6f, 0.6f, 1.62f, "minecraft:zombie_villager_v2"), WITCH(RaidParticipantEntity.class, 45, 1.8f, 0.6f, 0.6f, 1.62f), STRAY(AbstractSkeletonEntity.class, 46, 1.8f, 0.6f, 0.6f, 1.62f), HUSK(ZombieEntity.class, 47, 1.8f, 0.6f, 0.6f, 1.62f), @@ -153,7 +153,7 @@ public enum EntityType { FOX(FoxEntity.class, 121, 0.5f, 1.25f), BEE(BeeEntity.class, 122, 0.6f, 0.6f), STRIDER(StriderEntity.class, 125, 1.7f, 0.9f, 0f, 0f, "minecraft:strider"), - HOGLIN(AnimalEntity.class, 124, 1.4f, 1.3965f, 1.3965f, 0f, "minecraft:hoglin"), + HOGLIN(HoglinEntity.class, 124, 1.4f, 1.3965f, 1.3965f, 0f, "minecraft:hoglin"), ZOGLIN(ZoglinEntity.class, 126, 1.4f, 1.3965f, 1.3965f, 0f, "minecraft:zoglin"), PIGLIN(PiglinEntity.class, 123, 1.95f, 0.6f, 0.6f, 0f, "minecraft:piglin"), PIGLIN_BRUTE(BasePiglinEntity.class, 127, 1.95f, 0.6f, 0.6f, 0f, "minecraft:piglin_brute"), diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 4429268a..a6085e21 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -146,6 +146,13 @@ public class GeyserSession implements CommandSender { @Setter private boolean jumping; + /** + * The dimension of the player. + * As all entities are in the same world, this can be safely applied to all other entities. + */ + @Setter + private String dimension = DimensionUtils.OVERWORLD; + @Setter private int breakingBlock; @@ -629,7 +636,7 @@ public class GeyserSession implements CommandSender { startGamePacket.setRotation(Vector2f.from(1, 1)); startGamePacket.setSeed(-1); - startGamePacket.setDimensionId(DimensionUtils.javaToBedrock(playerEntity.getDimension())); + startGamePacket.setDimensionId(DimensionUtils.javaToBedrock(dimension)); startGamePacket.setGeneratorId(1); startGamePacket.setLevelGameType(GameType.SURVIVAL); startGamePacket.setDifficulty(1); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockInteractTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockInteractTranslator.java index c5d6f2dd..6c03cd03 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockInteractTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockInteractTranslator.java @@ -25,27 +25,65 @@ package org.geysermc.connector.network.translators.bedrock.entity.player; -import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import com.nukkitx.protocol.bedrock.data.entity.EntityDataMap; -import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; -import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket; -import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; - import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; import com.github.steveice10.mc.protocol.data.game.entity.player.InteractAction; import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerState; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerInteractEntityPacket; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerStatePacket; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityDataMap; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket; import com.nukkitx.protocol.bedrock.packet.InteractPacket; +import lombok.Getter; +import org.geysermc.connector.entity.Entity; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; +import org.geysermc.connector.network.translators.item.ItemEntry; import org.geysermc.connector.network.translators.item.ItemRegistry; +import java.util.Arrays; +import java.util.List; + @Translator(packet = InteractPacket.class) public class BedrockInteractTranslator extends PacketTranslator { + /** + * A list of all foods a horse/donkey can eat on Java Edition. + * Used to display interactive tag if needed. + */ + private static final List DONKEY_AND_HORSE_FOODS = Arrays.asList("golden_apple", "enchanted_golden_apple", + "golden_carrot", "sugar", "apple", "wheat", "hay_block"); + + /** + * A list of all flowers. Used for feeding bees. + */ + private static final List FLOWERS = Arrays.asList("dandelion", "poppy", "blue_orchid", "allium", "azure_bluet", + "red_tulip", "pink_tulip", "white_tulip", "orange_tulip", "cornflower", "lily_of_the_valley", "wither_rose", + "sunflower", "lilac", "rose_bush", "peony"); + + /** + * All entity types that can be leashed on Java Edition + */ + private static final List LEASHABLE_MOB_TYPES = Arrays.asList(EntityType.BEE, EntityType.CAT, EntityType.CHICKEN, + EntityType.COW, EntityType.DOLPHIN, EntityType.DONKEY, EntityType.FOX, EntityType.HOGLIN, EntityType.HORSE, EntityType.SKELETON_HORSE, + EntityType.ZOMBIE_HORSE, EntityType.IRON_GOLEM, EntityType.LLAMA, EntityType.TRADER_LLAMA, EntityType.MOOSHROOM, + EntityType.MULE, EntityType.OCELOT, EntityType.PARROT, EntityType.PIG, EntityType.POLAR_BEAR, EntityType.RABBIT, + EntityType.SHEEP, EntityType.SNOW_GOLEM, EntityType.STRIDER, EntityType.WOLF, EntityType.ZOGLIN); + + private static final List SADDLEABLE_WHEN_TAMED_MOB_TYPES = Arrays.asList(EntityType.DONKEY, EntityType.HORSE, + EntityType.ZOMBIE_HORSE, EntityType.MULE); + /** + * A list of all foods a wolf can eat on Java Edition. + * Used to display interactive tag if needed. + */ + private static final List WOLF_FOODS = Arrays.asList("pufferfish", "tropical_fish", "chicken", "cooked_chicken", + "porkchop", "beef", "rabbit", "cooked_porkchop", "cooked_beef", "rotten_flesh", "mutton", "cooked_mutton", + "cooked_rabbit"); + @Override public void translate(InteractPacket packet, GeyserSession session) { Entity entity; @@ -84,50 +122,232 @@ public class BedrockInteractTranslator extends PacketTranslator if (interactEntity == null) return; EntityDataMap entityMetadata = interactEntity.getMetadata(); + ItemEntry itemEntry = session.getInventory().getItemInHand() == null ? ItemEntry.AIR : ItemRegistry.getItem(session.getInventory().getItemInHand()); + String javaIdentifierStripped = itemEntry.getJavaIdentifier().replace("minecraft:", ""); - String interactiveTag; - switch (interactEntity.getEntityType()) { - case BOAT: - interactiveTag = "action.interact.ride.boat"; - break; - case DONKEY: - case HORSE: - case LLAMA: - case MULE: - case SKELETON_HORSE: - case TRADER_LLAMA: - case ZOMBIE_HORSE: - if (entityMetadata.getFlags().getFlag(EntityFlag.TAMED)) { - interactiveTag = "action.interact.ride.horse"; - } else { - interactiveTag = "action.interact.mount"; - } - break; - case MINECART: - interactiveTag = "action.interact.ride.minecart"; - break; - case PIG: - if (entityMetadata.getFlags().getFlag(EntityFlag.SADDLED)) { - interactiveTag = "action.interact.mount"; - } else interactiveTag = ""; - break; - case VILLAGER: - if (entityMetadata.getInt(EntityData.VARIANT) != 14 && entityMetadata.getInt(EntityData.VARIANT) != 0 - && entityMetadata.getFloat(EntityData.SCALE) >= 0.75f) { // Not a nitwit, has a profession and is not a baby - interactiveTag = "action.interact.trade"; - } else interactiveTag = ""; - break; - case WANDERING_TRADER: - interactiveTag = "action.interact.trade"; // Since you can always trade with a wandering villager, presumably. - break; - default: - return; // No need to process any further since there is no interactive tag + // TODO - in the future, update these in the metadata? So the client doesn't have to wiggle their cursor around for it to happen + // TODO - also, might be good to abstract out the eating thing. I know there will need to be food tracked for https://github.com/GeyserMC/Geyser/issues/1005 but not all food is breeding food + InteractiveTag interactiveTag = InteractiveTag.NONE; + if (entityMetadata.getLong(EntityData.LEASH_HOLDER_EID) == session.getPlayerEntity().getGeyserId()) { + // Unleash the entity + interactiveTag = InteractiveTag.REMOVE_LEASH; + } else if (javaIdentifierStripped.equals("saddle") && !entityMetadata.getFlags().getFlag(EntityFlag.SADDLED) && + ((SADDLEABLE_WHEN_TAMED_MOB_TYPES.contains(interactEntity.getEntityType()) && entityMetadata.getFlags().getFlag(EntityFlag.TAMED)) || + interactEntity.getEntityType() == EntityType.PIG || interactEntity.getEntityType() == EntityType.STRIDER)) { + // Entity can be saddled and the conditions meet (entity can be saddled and, if needed, is tamed) + interactiveTag = InteractiveTag.SADDLE; + } else if (javaIdentifierStripped.equals("name_tag") && session.getInventory().getItemInHand().getNbt() != null && + session.getInventory().getItemInHand().getNbt().contains("display")) { + // Holding a named name tag + interactiveTag = InteractiveTag.NAME; + } else if (javaIdentifierStripped.equals("lead") && LEASHABLE_MOB_TYPES.contains(interactEntity.getEntityType()) && + entityMetadata.getLong(EntityData.LEASH_HOLDER_EID) == -1L) { + // Holding a leash and the mob is leashable for sure + // (Plugins can change this behavior so that's something to look into in the far far future) + interactiveTag = InteractiveTag.LEASH; + } else { + switch (interactEntity.getEntityType()) { + case BEE: + if (FLOWERS.contains(javaIdentifierStripped)) { + interactiveTag = InteractiveTag.FEED; + } + break; + case BOAT: + interactiveTag = InteractiveTag.BOARD_BOAT; + break; + case CAT: + if (javaIdentifierStripped.equals("cod") || javaIdentifierStripped.equals("salmon")) { + interactiveTag = InteractiveTag.FEED; + } else if (entityMetadata.getFlags().getFlag(EntityFlag.TAMED) && + entityMetadata.getLong(EntityData.OWNER_EID) == session.getPlayerEntity().getGeyserId()) { + // Tamed and owned by player - can sit/stand + interactiveTag = entityMetadata.getFlags().getFlag(EntityFlag.SITTING) ? InteractiveTag.STAND : InteractiveTag.SIT; + break; + } + break; + case CHICKEN: + if (javaIdentifierStripped.contains("seeds")) { + interactiveTag = InteractiveTag.FEED; + } + break; + case MOOSHROOM: + // Shear the mooshroom + if (javaIdentifierStripped.equals("shears")) { + interactiveTag = InteractiveTag.MOOSHROOM_SHEAR; + break; + } + // Bowls are acceptable here + else if (javaIdentifierStripped.equals("bowl")) { + interactiveTag = InteractiveTag.MOOSHROOM_MILK_STEW; + break; + } + // Fall down to COW as this works on mooshrooms + case COW: + if (javaIdentifierStripped.equals("wheat")) { + interactiveTag = InteractiveTag.FEED; + } else if (javaIdentifierStripped.equals("bucket")) { + // Milk the cow + interactiveTag = InteractiveTag.MILK; + } + break; + case CREEPER: + if (javaIdentifierStripped.equals("flint_and_steel")) { + // Today I learned that you can ignite a creeper with flint and steel! Huh. + interactiveTag = InteractiveTag.IGNITE_CREEPER; + } + break; + case DONKEY: + case LLAMA: + case MULE: + if (entityMetadata.getFlags().getFlag(EntityFlag.TAMED) && !entityMetadata.getFlags().getFlag(EntityFlag.CHESTED) + && javaIdentifierStripped.equals("chest")) { + // Can attach a chest + interactiveTag = InteractiveTag.ATTACH_CHEST; + break; + } + // Intentional fall-through + case HORSE: + case SKELETON_HORSE: + case TRADER_LLAMA: + case ZOMBIE_HORSE: + // have another switch statement as, while these share mount attributes they don't share food + switch (interactEntity.getEntityType()) { + case LLAMA: + case TRADER_LLAMA: + if (javaIdentifierStripped.equals("wheat") || javaIdentifierStripped.equals("hay_block")) { + interactiveTag = InteractiveTag.FEED; + break; + } + case DONKEY: + case HORSE: + // Undead can't eat + if (DONKEY_AND_HORSE_FOODS.contains(javaIdentifierStripped)) { + interactiveTag = InteractiveTag.FEED; + break; + } + } + if (!entityMetadata.getFlags().getFlag(EntityFlag.BABY)) { + // Can't ride a baby + if (entityMetadata.getFlags().getFlag(EntityFlag.TAMED)) { + interactiveTag = InteractiveTag.RIDE_HORSE; + } else if (!entityMetadata.getFlags().getFlag(EntityFlag.TAMED) && itemEntry.equals(ItemEntry.AIR)) { + // Can't hide an untamed entity without having your hand empty + interactiveTag = InteractiveTag.MOUNT; + } + } + break; + case FOX: + if (javaIdentifierStripped.equals("sweet_berries")) { + interactiveTag = InteractiveTag.FEED; + } + break; + case HOGLIN: + if (javaIdentifierStripped.equals("crimson_fungus")) { + interactiveTag = InteractiveTag.FEED; + } + break; + case MINECART: + interactiveTag = InteractiveTag.RIDE_MINECART; + break; + case MINECART_CHEST: + case MINECART_COMMAND_BLOCK: + case MINECART_HOPPER: + interactiveTag = InteractiveTag.OPEN_CONTAINER; + break; + case OCELOT: + if (javaIdentifierStripped.equals("cod") || javaIdentifierStripped.equals("salmon")) { + interactiveTag = InteractiveTag.FEED; + } + break; + case PANDA: + if (javaIdentifierStripped.equals("bamboo")) { + interactiveTag = InteractiveTag.FEED; + } + break; + case PARROT: + if (javaIdentifierStripped.contains("seeds") || javaIdentifierStripped.equals("cookie")) { + interactiveTag = InteractiveTag.FEED; + } + break; + case PIG: + if (javaIdentifierStripped.equals("carrot") || javaIdentifierStripped.equals("potato") || javaIdentifierStripped.equals("beetroot")) { + interactiveTag = InteractiveTag.FEED; + } else if (entityMetadata.getFlags().getFlag(EntityFlag.SADDLED)) { + interactiveTag = InteractiveTag.MOUNT; + } + break; + case PIGLIN: + if (!entityMetadata.getFlags().getFlag(EntityFlag.BABY) && javaIdentifierStripped.equals("gold_ingot")) { + interactiveTag = InteractiveTag.BARTER; + } + break; + case RABBIT: + if (javaIdentifierStripped.equals("dandelion") || javaIdentifierStripped.equals("carrot") || javaIdentifierStripped.equals("golden_carrot")) { + interactiveTag = InteractiveTag.FEED; + } + break; + case SHEEP: + if (javaIdentifierStripped.equals("wheat")) { + interactiveTag = InteractiveTag.FEED; + } else if (!entityMetadata.getFlags().getFlag(EntityFlag.SHEARED)) { + if (javaIdentifierStripped.equals("shears")) { + // Shear the sheep + interactiveTag = InteractiveTag.SHEAR; + } else if (javaIdentifierStripped.contains("_dye")) { + // Dye the sheep + interactiveTag = InteractiveTag.DYE; + } + } + break; + case STRIDER: + if (javaIdentifierStripped.equals("warped_fungus")) { + interactiveTag = InteractiveTag.FEED; + } else if (entityMetadata.getFlags().getFlag(EntityFlag.SADDLED)) { + interactiveTag = InteractiveTag.RIDE_STRIDER; + } + break; + case TURTLE: + if (javaIdentifierStripped.equals("seagrass")) { + interactiveTag = InteractiveTag.FEED; + } + break; + case VILLAGER: + if (entityMetadata.getInt(EntityData.VARIANT) != 14 && entityMetadata.getInt(EntityData.VARIANT) != 0 + && entityMetadata.getFloat(EntityData.SCALE) >= 0.75f) { // Not a nitwit, has a profession and is not a baby + interactiveTag = InteractiveTag.TRADE; + } + break; + case WANDERING_TRADER: + interactiveTag = InteractiveTag.TRADE; // Since you can always trade with a wandering villager, presumably. + break; + case WOLF: + if (javaIdentifierStripped.equals("bone") && !entityMetadata.getFlags().getFlag(EntityFlag.TAMED)) { + // Bone and untamed - can tame + interactiveTag = InteractiveTag.TAME; + } else if (WOLF_FOODS.contains(javaIdentifierStripped)) { + // Compatible food in hand - feed + // Sometimes just sits/stands when the wolf isn't hungry - there doesn't appear to be a way to fix this + interactiveTag = InteractiveTag.FEED; + } else if (entityMetadata.getFlags().getFlag(EntityFlag.TAMED) && + entityMetadata.getLong(EntityData.OWNER_EID) == session.getPlayerEntity().getGeyserId()) { + // Tamed and owned by player - can sit/stand + interactiveTag = entityMetadata.getFlags().getFlag(EntityFlag.SITTING) ? InteractiveTag.STAND : InteractiveTag.SIT; + } + break; + case ZOMBIE_VILLAGER: + // We can't guarantee the existence of the weakness effect so we just always show it. + if (javaIdentifierStripped.equals("golden_apple")) { + interactiveTag = InteractiveTag.CURE; + } + break; + default: + break; + } } - session.getPlayerEntity().getMetadata().put(EntityData.INTERACTIVE_TAG, interactiveTag); + session.getPlayerEntity().getMetadata().put(EntityData.INTERACTIVE_TAG, interactiveTag.getValue()); session.getPlayerEntity().updateBedrockMetadata(session); } else { - if (!(session.getPlayerEntity().getMetadata().get(EntityData.INTERACTIVE_TAG) == null) || - !(session.getPlayerEntity().getMetadata().get(EntityData.INTERACTIVE_TAG) == "")) { + if (!session.getPlayerEntity().getMetadata().getString(EntityData.INTERACTIVE_TAG).isEmpty()) { // No interactive tag should be sent session.getPlayerEntity().getMetadata().remove(EntityData.INTERACTIVE_TAG); session.getPlayerEntity().updateBedrockMetadata(session); @@ -147,4 +367,65 @@ public class BedrockInteractTranslator extends PacketTranslator break; } } + + /** + * All interactive tags in enum form. For potential API usage. + */ + public enum InteractiveTag { + NONE(true), + IGNITE_CREEPER("creeper"), + EDIT, + LEAVE_BOAT("exit.boat"), + FEED, + FISH("fishing"), + MILK, + MOOSHROOM_SHEAR("mooshear"), + MOOSHROOM_MILK_STEW("moostew"), + BOARD_BOAT("ride.boat"), + RIDE_MINECART("ride.minecart"), + RIDE_HORSE("ride.horse"), + RIDE_STRIDER("ride.strider"), + SHEAR, + SIT, + STAND, + TALK, + TAME, + DYE, + CURE, + OPEN_CONTAINER("opencontainer"), + CREATE_MAP("createMap"), + TAKE_PICTURE("takepicture"), + SADDLE, + MOUNT, + BOOST, + WRITE, + LEASH, + REMOVE_LEASH("unleash"), + NAME, + ATTACH_CHEST("attachchest"), + TRADE, + POSE_ARMOR_STAND("armorstand.pose"), + EQUIP_ARMOR_STAND("armorstand.equip"), + READ, + WAKE_VILLAGER("wakevillager"), + BARTER; + + /** + * The full string that should be passed on to the client. + */ + @Getter + private final String value; + + InteractiveTag(boolean isNone) { + this.value = ""; + } + + InteractiveTag(String value) { + this.value = "action.interact." + value; + } + + InteractiveTag() { + this.value = "action.interact." + name().toLowerCase(); + } + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java index 8370ba8e..18103087 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java @@ -55,6 +55,10 @@ public class ItemRegistry { public static final List ITEMS = new ArrayList<>(); public static final Int2ObjectMap ITEM_ENTRIES = new Int2ObjectOpenHashMap<>(); + /** + * Bamboo item entry, used in PandaEntity.java + */ + public static ItemEntry BAMBOO; /** * Boat item entry, used in BedrockInventoryTransactionTranslator.java */ @@ -146,6 +150,9 @@ public class ItemRegistry { case "minecraft:barrier": BARRIER_INDEX = itemIndex; break; + case "minecraft:bamboo": + BAMBOO = ITEM_ENTRIES.get(itemIndex); + break; case "minecraft:oak_boat": BOAT = ITEM_ENTRIES.get(itemIndex); break; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java index a86c1a97..35ca7928 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java @@ -55,7 +55,7 @@ public class JavaJoinGameTranslator extends PacketTranslator } String newDimension = DimensionUtils.getNewDimension(packet.getDimension()); - if (!entity.getDimension().equals(newDimension)) { + if (!session.getDimension().equals(newDimension)) { DimensionUtils.switchDimension(session, newDimension); } else { if (session.isManyDimPackets()) { //reloading world - String fakeDim = entity.getDimension().equals(DimensionUtils.OVERWORLD) ? DimensionUtils.NETHER : DimensionUtils.OVERWORLD; + String fakeDim = session.getDimension().equals(DimensionUtils.OVERWORLD) ? DimensionUtils.NETHER : DimensionUtils.OVERWORLD; DimensionUtils.switchDimension(session, fakeDim); DimensionUtils.switchDimension(session, newDimension); } else { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityAttachTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityAttachTranslator.java index b642a75b..1a6630ef 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityAttachTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityAttachTranslator.java @@ -62,7 +62,7 @@ public class JavaEntityAttachTranslator extends PacketTranslator { @@ -141,9 +137,15 @@ public class JavaEntityStatusTranslator extends PacketTranslator boolean shouldStore = false; mapItemDataPacket.setUniqueMapId(packet.getMapId()); - mapItemDataPacket.setDimensionId(DimensionUtils.javaToBedrock(session.getPlayerEntity().getDimension())); + mapItemDataPacket.setDimensionId(DimensionUtils.javaToBedrock(session.getDimension())); mapItemDataPacket.setLocked(packet.isLocked()); mapItemDataPacket.setScale(packet.getScale()); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaSpawnParticleTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaSpawnParticleTranslator.java index 52d3f649..4620fc11 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaSpawnParticleTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaSpawnParticleTranslator.java @@ -94,7 +94,7 @@ public class JavaSpawnParticleTranslator extends PacketTranslator localeStrings = LocaleUtils.LOCALE_MAPPINGS.get(locale.toLowerCase()); - if (localeStrings == null) - localeStrings = LocaleUtils.LOCALE_MAPPINGS.get(LanguageUtils.getDefaultLocale()); if (localeStrings == null) { - // Don't cause a NPE if the locale is STILL missing - GeyserConnector.getInstance().getLogger().debug("MISSING DEFAULT LOCALE: " + LanguageUtils.getDefaultLocale()); - return messageText; + localeStrings = LocaleUtils.LOCALE_MAPPINGS.get(LanguageUtils.getDefaultLocale()); + if (localeStrings == null) { + // Don't cause a NPE if the locale is STILL missing + GeyserConnector.getInstance().getLogger().debug("MISSING DEFAULT LOCALE: " + LanguageUtils.getDefaultLocale()); + return messageText; + } } return localeStrings.getOrDefault(messageText, messageText); From 80cf407faee7306252e600ab7cf9563229ee8088 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Fri, 13 Nov 2020 23:00:09 -0500 Subject: [PATCH 109/205] Update MCProtocolLib to fix more custom recipe stuff (#1534) --- connector/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/pom.xml b/connector/pom.xml index ee3164e1..d837f057 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -111,7 +111,7 @@ com.github.steveice10 mcprotocollib - 6ac79c14d6 + 86e1901be5 compile From bf05f64fac2cedcb8bb41f6c44a50581029dea6b Mon Sep 17 00:00:00 2001 From: Niklas <7442307+niklaswa@users.noreply.github.com> Date: Sat, 14 Nov 2020 18:52:10 +0100 Subject: [PATCH 110/205] Update LabyMod cape url (#1540) --- .../main/java/org/geysermc/connector/utils/SkinProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java b/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java index 82fc3a3a..d848d95e 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java @@ -607,7 +607,7 @@ public class SkinProvider { public enum CapeProvider { MINECRAFT, OPTIFINE("https://optifine.net/capes/%s.png", CapeUrlType.USERNAME), - LABYMOD("https://www.labymod.net/page/php/getCapeTexture.php?uuid=%s", CapeUrlType.UUID_DASHED), + LABYMOD("https://dl.labymod.net/capes/%s", CapeUrlType.UUID_DASHED), FIVEZIG("https://textures.5zigreborn.eu/profile/%s", CapeUrlType.UUID_DASHED), MINECRAFTCAPES("https://minecraftcapes.net/profile/%s/cape", CapeUrlType.UUID); From 445444204f017ba4b704478efc4a97c398da2ed2 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Sat, 14 Nov 2020 13:05:33 -0500 Subject: [PATCH 111/205] Fix GlobalPalette translation (#1528) * Fix GlobalPalette translation Global palettes don't have their own internal palette, which cannot be iterated through to create a Bedrock palette. Therefore we simply iterate over the whole palette one time. This commit also fixes a regression with flowers/pistons being on multiple chunk sections. * Don't declare bedrockPalette until after global palette check --- .../geysermc/connector/utils/ChunkUtils.java | 37 ++++++++++++++++--- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java index 0769a4d1..70ac76df 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java @@ -28,6 +28,7 @@ package org.geysermc.connector.utils; import com.github.steveice10.mc.protocol.data.game.chunk.BitStorage; import com.github.steveice10.mc.protocol.data.game.chunk.Chunk; import com.github.steveice10.mc.protocol.data.game.chunk.Column; +import com.github.steveice10.mc.protocol.data.game.chunk.palette.GlobalPalette; import com.github.steveice10.mc.protocol.data.game.chunk.palette.Palette; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; @@ -65,10 +66,10 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.BitSet; -import java.util.Collections; import java.util.List; -import static org.geysermc.connector.network.translators.world.block.BlockTranslator.*; +import static org.geysermc.connector.network.translators.world.block.BlockTranslator.AIR; +import static org.geysermc.connector.network.translators.world.block.BlockTranslator.BEDROCK_WATER_ID; @UtilityClass public class ChunkUtils { @@ -107,7 +108,7 @@ public class ChunkUtils { ChunkSection[] sections = new ChunkSection[javaSections.length]; // Temporarily stores compound tags of Bedrock-only block entities - List bedrockOnlyBlockEntities = Collections.emptyList(); + List bedrockOnlyBlockEntities = new ArrayList<>(); BitSet waterloggedPaletteIds = new BitSet(); BitSet pistonOrFlowerPaletteIds = new BitSet(); @@ -155,6 +156,33 @@ public class ChunkUtils { } Palette javaPalette = javaSection.getPalette(); + BitStorage javaData = javaSection.getStorage(); + + if (javaPalette instanceof GlobalPalette) { + // As this is the global palette, simply iterate through the whole chunk section once + ChunkSection section = new ChunkSection(); + for (int yzx = 0; yzx < BlockStorage.SIZE; yzx++) { + int javaId = javaData.get(yzx); + int bedrockId = BlockTranslator.getBedrockBlockId(javaId); + int xzy = indexYZXtoXZY(yzx); + section.getBlockStorageArray()[0].setFullBlock(xzy, bedrockId); + + if (BlockTranslator.isWaterlogged(javaId)) { + section.getBlockStorageArray()[1].setFullBlock(xzy, BEDROCK_WATER_ID); + } + + // Check if block is piston or flower to see if we'll need to create additional block entities, as they're only block entities in Bedrock + if (BlockStateValues.getFlowerPotValues().containsKey(javaId) || BlockStateValues.getPistonValues().containsKey(javaId)) { + bedrockOnlyBlockEntities.add(BedrockOnlyBlockEntity.getTag( + Vector3i.from((column.getX() << 4) + (yzx & 0xF), (sectionY << 4) + ((yzx >> 8) & 0xF), (column.getZ() << 4) + ((yzx >> 4) & 0xF)), + javaId + )); + } + } + sections[sectionY] = section; + continue; + } + IntList bedrockPalette = new IntArrayList(javaPalette.size()); waterloggedPaletteIds.clear(); pistonOrFlowerPaletteIds.clear(); @@ -174,13 +202,10 @@ public class ChunkUtils { } } - BitStorage javaData = javaSection.getStorage(); - // Add Bedrock-exclusive block entities // We only if the palette contained any blocks that are Bedrock-exclusive block entities to avoid iterating through the whole block data // for no reason, as most sections will not contain any pistons or flower pots if (!pistonOrFlowerPaletteIds.isEmpty()) { - bedrockOnlyBlockEntities = new ArrayList<>(); for (int yzx = 0; yzx < BlockStorage.SIZE; yzx++) { int paletteId = javaData.get(yzx); if (pistonOrFlowerPaletteIds.get(paletteId)) { From 981ac3bf118f331ded9fe4bcbd0e2f9e90792750 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sat, 14 Nov 2020 17:49:56 -0600 Subject: [PATCH 112/205] Move PlatformType to common module --- .../org/geysermc/platform/bungeecord/GeyserBungeePlugin.java | 2 +- .../java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java | 2 +- .../java/org/geysermc/platform/sponge/GeyserSpongePlugin.java | 2 +- .../geysermc/platform/standalone/GeyserStandaloneBootstrap.java | 2 +- .../org/geysermc/platform/velocity/GeyserVelocityPlugin.java | 2 +- .../src/main/java/org/geysermc}/common/PlatformType.java | 2 +- .../src/main/java/org/geysermc/connector/GeyserConnector.java | 2 +- .../org/geysermc/connector/command/defaults/ReloadCommand.java | 2 +- .../org/geysermc/connector/command/defaults/StopCommand.java | 2 +- .../java/org/geysermc/connector/dump/BootstrapDumpInfo.java | 2 +- .../translators/bedrock/BedrockCommandRequestTranslator.java | 2 +- .../translators/java/world/JavaBlockChangeTranslator.java | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) rename {connector/src/main/java/org/geysermc/connector => common/src/main/java/org/geysermc}/common/PlatformType.java (97%) diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java index abb9789e..a65646bf 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java @@ -27,10 +27,10 @@ package org.geysermc.platform.bungeecord; import net.md_5.bungee.api.config.ListenerInfo; import net.md_5.bungee.api.plugin.Plugin; +import org.geysermc.common.PlatformType; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.bootstrap.GeyserBootstrap; import org.geysermc.connector.command.CommandManager; -import org.geysermc.connector.common.PlatformType; import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.dump.BootstrapDumpInfo; import org.geysermc.connector.ping.GeyserLegacyPingPassthrough; diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java index 59a8db06..741763ec 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java @@ -27,10 +27,10 @@ package org.geysermc.platform.spigot; import org.bukkit.Bukkit; import org.bukkit.plugin.java.JavaPlugin; +import org.geysermc.common.PlatformType; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.bootstrap.GeyserBootstrap; import org.geysermc.connector.command.CommandManager; -import org.geysermc.connector.common.PlatformType; import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.dump.BootstrapDumpInfo; import org.geysermc.connector.network.translators.world.WorldManager; diff --git a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java index 106d2b15..c3231f3b 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java @@ -26,10 +26,10 @@ package org.geysermc.platform.sponge; import com.google.inject.Inject; +import org.geysermc.common.PlatformType; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.bootstrap.GeyserBootstrap; import org.geysermc.connector.command.CommandManager; -import org.geysermc.connector.common.PlatformType; import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.dump.BootstrapDumpInfo; import org.geysermc.connector.ping.GeyserLegacyPingPassthrough; diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java index f4dfd454..a0a8a3ae 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java @@ -36,10 +36,10 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.core.Appender; import org.apache.logging.log4j.core.Logger; import org.apache.logging.log4j.core.appender.ConsoleAppender; +import org.geysermc.common.PlatformType; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.bootstrap.GeyserBootstrap; import org.geysermc.connector.command.CommandManager; -import org.geysermc.connector.common.PlatformType; import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.configuration.GeyserJacksonConfiguration; import org.geysermc.connector.dump.BootstrapDumpInfo; diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java index f75c683a..b5255e62 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java @@ -33,9 +33,9 @@ import com.velocitypowered.api.event.proxy.ProxyShutdownEvent; import com.velocitypowered.api.plugin.Plugin; import com.velocitypowered.api.proxy.ProxyServer; import lombok.Getter; +import org.geysermc.common.PlatformType; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.bootstrap.GeyserBootstrap; -import org.geysermc.connector.common.PlatformType; import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.dump.BootstrapDumpInfo; import org.geysermc.connector.ping.GeyserLegacyPingPassthrough; diff --git a/connector/src/main/java/org/geysermc/connector/common/PlatformType.java b/common/src/main/java/org/geysermc/common/PlatformType.java similarity index 97% rename from connector/src/main/java/org/geysermc/connector/common/PlatformType.java rename to common/src/main/java/org/geysermc/common/PlatformType.java index 3e945d3a..88349020 100644 --- a/connector/src/main/java/org/geysermc/connector/common/PlatformType.java +++ b/common/src/main/java/org/geysermc/common/PlatformType.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.common; +package org.geysermc.common; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 2497166c..c461fa96 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -32,10 +32,10 @@ import com.nukkitx.network.raknet.RakNetConstants; import com.nukkitx.protocol.bedrock.BedrockServer; import lombok.Getter; import lombok.Setter; +import org.geysermc.common.PlatformType; import org.geysermc.connector.bootstrap.GeyserBootstrap; import org.geysermc.connector.command.CommandManager; import org.geysermc.connector.common.AuthType; -import org.geysermc.connector.common.PlatformType; import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.metrics.Metrics; import org.geysermc.connector.network.ConnectorServerEventHandler; diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/ReloadCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/ReloadCommand.java index 6b2be294..64d0017e 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/ReloadCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/ReloadCommand.java @@ -25,7 +25,7 @@ package org.geysermc.connector.command.defaults; -import org.geysermc.connector.common.PlatformType; +import org.geysermc.common.PlatformType; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.CommandSender; import org.geysermc.connector.command.GeyserCommand; diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/StopCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/StopCommand.java index c69a8705..5be6253e 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/StopCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/StopCommand.java @@ -25,7 +25,7 @@ package org.geysermc.connector.command.defaults; -import org.geysermc.connector.common.PlatformType; +import org.geysermc.common.PlatformType; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.CommandSender; import org.geysermc.connector.command.GeyserCommand; diff --git a/connector/src/main/java/org/geysermc/connector/dump/BootstrapDumpInfo.java b/connector/src/main/java/org/geysermc/connector/dump/BootstrapDumpInfo.java index 9fbe82cc..58556553 100644 --- a/connector/src/main/java/org/geysermc/connector/dump/BootstrapDumpInfo.java +++ b/connector/src/main/java/org/geysermc/connector/dump/BootstrapDumpInfo.java @@ -27,7 +27,7 @@ package org.geysermc.connector.dump; import lombok.AllArgsConstructor; import lombok.Getter; -import org.geysermc.connector.common.PlatformType; +import org.geysermc.common.PlatformType; import org.geysermc.connector.GeyserConnector; import java.util.List; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandRequestTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandRequestTranslator.java index d05a667d..1f31367c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandRequestTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandRequestTranslator.java @@ -25,7 +25,7 @@ package org.geysermc.connector.network.translators.bedrock; -import org.geysermc.connector.common.PlatformType; +import org.geysermc.common.PlatformType; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.CommandManager; import org.geysermc.connector.network.session.GeyserSession; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockChangeTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockChangeTranslator.java index 9e81ce59..50705ae2 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockChangeTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockChangeTranslator.java @@ -29,7 +29,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.SoundEvent; import com.nukkitx.protocol.bedrock.packet.LevelSoundEventPacket; -import org.geysermc.connector.common.PlatformType; +import org.geysermc.common.PlatformType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; From 47f25f1205f360a690a7510b2e79fc7c30ead61a Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Sun, 15 Nov 2020 18:48:11 -0500 Subject: [PATCH 113/205] BannerTranslator: fix NPE when no block entity data (#1543) --- .../network/translators/item/translators/BannerTranslator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java index 200271cf..14b93436 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java @@ -130,7 +130,7 @@ public class BannerTranslator extends ItemTranslator { ItemData itemData = super.translateToBedrock(itemStack, itemEntry); CompoundTag blockEntityTag = itemStack.getNbt().get("BlockEntityTag"); - if (blockEntityTag.contains("Patterns")) { + if (blockEntityTag != null && blockEntityTag.contains("Patterns")) { ListTag patterns = blockEntityTag.get("Patterns"); NbtMapBuilder builder = itemData.getTag().toBuilder(); From 512f8cd6c2cf002aaf8e3087c361784b8c603fd2 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Mon, 16 Nov 2020 23:57:57 +0000 Subject: [PATCH 114/205] Rewrite message handling in MessageUtils to use Adventure (#1498) * Rewrite message handling in MessageUtils to use Adventure * Move to static Adventure commit to fix a bug * Initial test implementation * Add RGB downgrade test * Move MessageUtils and rename * Clean-up and fix tests * Fixed sign and book content handling * Fix blank signs causing NPEs * Fix reset before message being stripped * Add comment about the reset character * Fix legacy style server motds * Fix more messages being handled wrong * Fix title packets being handled wrong * Fix trailing formatting characters on the end of sign lines * Add auto updating of Java locale files * Add en_us locale updating and hash caching * Changes to hash determining Co-authored-by: DoctorMacc --- connector/pom.xml | 23 +- .../entity/CommandBlockMinecartEntity.java | 5 +- .../org/geysermc/connector/entity/Entity.java | 4 +- .../connector/entity/PlayerEntity.java | 6 +- .../network/ConnectorServerEventHandler.java | 5 +- .../connector/network/QueryPacketHandler.java | 5 +- .../network/session/GeyserSession.java | 4 +- .../network/session/cache/BossBar.java | 6 +- .../BedrockCommandRequestTranslator.java | 4 +- .../bedrock/BedrockTextTranslator.java | 4 +- .../translators/chat/MessageTranslator.java | 278 ++++++++++ .../chat/MinecraftTranslationRegistry.java | 81 +++ .../translators/item/ItemTranslator.java | 26 +- .../translators/nbt/BasicItemTranslator.java | 3 +- .../translators/nbt/BookPagesTranslator.java | 6 +- .../translators/java/JavaChatTranslator.java | 22 +- .../java/JavaDisconnectPacket.java | 4 +- .../java/JavaLoginDisconnectTranslator.java | 4 +- .../translators/java/JavaTitleTranslator.java | 16 +- .../JavaScoreboardObjectiveTranslator.java | 4 +- .../java/scoreboard/JavaTeamTranslator.java | 14 +- .../java/window/JavaOpenWindowTranslator.java | 6 +- .../CommandBlockBlockEntityTranslator.java | 4 +- .../entity/SignBlockEntityTranslator.java | 16 +- .../geysermc/connector/utils/FileUtils.java | 21 +- .../geysermc/connector/utils/LocaleUtils.java | 52 +- .../connector/utils/MessageUtils.java | 494 ------------------ .../chat/MessageTranslatorTest.java | 67 +++ 28 files changed, 583 insertions(+), 601 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/chat/MessageTranslator.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/chat/MinecraftTranslationRegistry.java delete mode 100644 connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java create mode 100644 connector/src/test/java/org/geysermc/connector/network/translators/chat/MessageTranslatorTest.java diff --git a/connector/pom.xml b/connector/pom.xml index d837f057..db267a71 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -143,17 +143,23 @@ compile - net.kyori + com.github.kyoripowered.adventure adventure-text-serializer-gson - 4.1.1 + 4d8a67d798 compile - net.kyori + com.github.kyoripowered.adventure adventure-text-serializer-legacy - 4.1.1 + 0599048 compile + + junit + junit + 4.13.1 + test + @@ -283,6 +289,15 @@ + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.0 + + + -Dfile.encoding=${project.build.sourceEncoding} + + diff --git a/connector/src/main/java/org/geysermc/connector/entity/CommandBlockMinecartEntity.java b/connector/src/main/java/org/geysermc/connector/entity/CommandBlockMinecartEntity.java index 8cabba64..7d34cc79 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/CommandBlockMinecartEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/CommandBlockMinecartEntity.java @@ -26,13 +26,12 @@ package org.geysermc.connector.entity; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.github.steveice10.mc.protocol.data.message.Message; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.block.BlockTranslator; -import org.geysermc.connector.utils.MessageUtils; +import org.geysermc.connector.network.translators.chat.MessageTranslator; public class CommandBlockMinecartEntity extends DefaultBlockMinecartEntity { @@ -51,7 +50,7 @@ public class CommandBlockMinecartEntity extends DefaultBlockMinecartEntity { metadata.put(EntityData.COMMAND_BLOCK_COMMAND, entityMetadata.getValue()); } if (entityMetadata.getId() == 14) { - metadata.put(EntityData.COMMAND_BLOCK_LAST_OUTPUT, MessageUtils.getBedrockMessage((Message) entityMetadata.getValue())); + metadata.put(EntityData.COMMAND_BLOCK_LAST_OUTPUT, MessageTranslator.convertMessage(entityMetadata.getValue().toString())); } super.updateBedrockMetadata(entityMetadata, session); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/Entity.java b/connector/src/main/java/org/geysermc/connector/entity/Entity.java index 20cd2f76..7b1fa1cf 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/Entity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/Entity.java @@ -54,7 +54,7 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.utils.AttributeUtils; import org.geysermc.connector.utils.ChunkUtils; -import org.geysermc.connector.utils.MessageUtils; +import org.geysermc.connector.network.translators.chat.MessageTranslator; import java.util.ArrayList; import java.util.HashMap; @@ -318,7 +318,7 @@ public class Entity { Message message = (Message) entityMetadata.getValue(); if (message != null) // Always translate even if it's a TextMessage since there could be translatable parameters - metadata.put(EntityData.NAMETAG, MessageUtils.getTranslatedBedrockMessage(message, session.getLocale(), true)); + metadata.put(EntityData.NAMETAG, MessageTranslator.convertMessage(message.toString(), session.getLocale())); } break; case 3: // is custom name visible diff --git a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java index 8eeae473..be65525c 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java @@ -51,7 +51,7 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.cache.EntityEffectCache; import org.geysermc.connector.scoreboard.Team; import org.geysermc.connector.utils.AttributeUtils; -import org.geysermc.connector.utils.MessageUtils; +import org.geysermc.connector.network.translators.chat.MessageTranslator; import java.util.ArrayList; import java.util.List; @@ -243,13 +243,13 @@ public class PlayerEntity extends LivingEntity { String username = this.username; TextMessage name = (TextMessage) entityMetadata.getValue(); if (name != null) { - username = MessageUtils.getBedrockMessage(name); + username = MessageTranslator.convertMessage(name.toString()); } Team team = session.getWorldCache().getScoreboard().getTeamFor(username); if (team != null) { String displayName = ""; if (team.isVisibleFor(session.getPlayerEntity().getUsername())) { - displayName = MessageUtils.toChatColor(team.getColor()) + username; + displayName = MessageTranslator.toChatColor(team.getColor()) + username; displayName = team.getCurrentData().getDisplayName(displayName); } metadata.put(EntityData.NAMETAG, displayName); diff --git a/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java b/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java index 9fb4ad9e..150d298c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java @@ -25,7 +25,6 @@ package org.geysermc.connector.network; -import com.github.steveice10.mc.protocol.data.message.MessageSerializer; import com.nukkitx.protocol.bedrock.BedrockPong; import com.nukkitx.protocol.bedrock.BedrockServerEventHandler; import com.nukkitx.protocol.bedrock.BedrockServerSession; @@ -36,7 +35,7 @@ import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.ping.IGeyserPingPassthrough; -import org.geysermc.connector.utils.MessageUtils; +import org.geysermc.connector.network.translators.chat.MessageTranslator; import org.geysermc.connector.utils.LanguageUtils; import java.net.InetSocketAddress; @@ -76,7 +75,7 @@ public class ConnectorServerEventHandler implements BedrockServerEventHandler { pong.setIpv4Port(config.getBedrock().getPort()); if (config.isPassthroughMotd() && pingInfo != null && pingInfo.getDescription() != null) { - String[] motd = MessageUtils.getBedrockMessage(MessageSerializer.fromString(pingInfo.getDescription())).split("\n"); + String[] motd = MessageTranslator.convertMessageLenient(pingInfo.getDescription()).split("\n"); String mainMotd = motd[0]; // First line of the motd. String subMotd = (motd.length != 1) ? motd[1] : ""; // Second line of the motd if present, otherwise blank. diff --git a/connector/src/main/java/org/geysermc/connector/network/QueryPacketHandler.java b/connector/src/main/java/org/geysermc/connector/network/QueryPacketHandler.java index 7faf36bd..510bba2d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/QueryPacketHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/QueryPacketHandler.java @@ -25,12 +25,11 @@ package org.geysermc.connector.network; -import com.github.steveice10.mc.protocol.data.message.MessageSerializer; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import org.geysermc.connector.common.ping.GeyserPingInfo; import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.utils.MessageUtils; +import org.geysermc.connector.network.translators.chat.MessageTranslator; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -148,7 +147,7 @@ public class QueryPacketHandler { } if (connector.getConfig().isPassthroughMotd() && pingInfo != null) { - String[] javaMotd = MessageUtils.getBedrockMessage(MessageSerializer.fromString(pingInfo.getDescription())).split("\n"); + String[] javaMotd = MessageTranslator.convertMessageLenient(pingInfo.getDescription()).split("\n"); motd = javaMotd[0].trim(); // First line of the motd. } else { motd = connector.getConfig().getBedrock().getMotd1(); diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index a6085e21..00b48a56 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -34,7 +34,6 @@ import com.github.steveice10.mc.protocol.data.SubProtocol; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import com.github.steveice10.mc.protocol.data.game.statistic.Statistic; import com.github.steveice10.mc.protocol.data.game.window.VillagerTrade; -import com.github.steveice10.mc.protocol.data.message.MessageSerializer; import com.github.steveice10.mc.protocol.packet.handshake.client.HandshakePacket; import com.github.steveice10.mc.protocol.packet.ingame.client.world.ClientTeleportConfirmPacket; import com.github.steveice10.mc.protocol.packet.ingame.server.ServerRespawnPacket; @@ -66,6 +65,7 @@ import org.geysermc.connector.common.AuthType; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.PlayerEntity; import org.geysermc.connector.inventory.PlayerInventory; +import org.geysermc.connector.network.translators.chat.MessageTranslator; import org.geysermc.connector.network.remote.RemoteServer; import org.geysermc.connector.network.session.auth.AuthData; import org.geysermc.connector.network.session.auth.BedrockClientData; @@ -496,7 +496,7 @@ public class GeyserSession implements CommandSender { event.getCause().printStackTrace(); } - upstream.disconnect(MessageUtils.getBedrockMessage(MessageSerializer.fromString(event.getReason()))); + upstream.disconnect(MessageTranslator.convertMessageLenient(event.getReason())); } @Override diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/BossBar.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/BossBar.java index fdc609ab..7eadb794 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/BossBar.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/BossBar.java @@ -33,7 +33,7 @@ import com.nukkitx.protocol.bedrock.packet.BossEventPacket; import com.nukkitx.protocol.bedrock.packet.RemoveEntityPacket; import lombok.AllArgsConstructor; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.utils.MessageUtils; +import org.geysermc.connector.network.translators.chat.MessageTranslator; @AllArgsConstructor public class BossBar { @@ -58,7 +58,7 @@ public class BossBar { BossEventPacket bossEventPacket = new BossEventPacket(); bossEventPacket.setBossUniqueEntityId(entityId); bossEventPacket.setAction(BossEventPacket.Action.CREATE); - bossEventPacket.setTitle(MessageUtils.getTranslatedBedrockMessage(title, session.getLocale())); + bossEventPacket.setTitle(MessageTranslator.convertMessage(title.toString(), session.getLocale())); bossEventPacket.setHealthPercentage(health); bossEventPacket.setColor(color); //ignored by client bossEventPacket.setOverlay(overlay); @@ -72,7 +72,7 @@ public class BossBar { BossEventPacket bossEventPacket = new BossEventPacket(); bossEventPacket.setBossUniqueEntityId(entityId); bossEventPacket.setAction(BossEventPacket.Action.UPDATE_NAME); - bossEventPacket.setTitle(MessageUtils.getTranslatedBedrockMessage(title, session.getLocale())); + bossEventPacket.setTitle(MessageTranslator.convertMessage(title.toString(), session.getLocale())); session.sendUpstreamPacket(bossEventPacket); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandRequestTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandRequestTranslator.java index 1f31367c..f572538e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandRequestTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandRequestTranslator.java @@ -34,7 +34,7 @@ import org.geysermc.connector.network.translators.Translator; import com.github.steveice10.mc.protocol.packet.ingame.client.ClientChatPacket; import com.nukkitx.protocol.bedrock.packet.CommandRequestPacket; -import org.geysermc.connector.utils.MessageUtils; +import org.geysermc.connector.network.translators.chat.MessageTranslator; @Translator(packet = CommandRequestPacket.class) public class BedrockCommandRequestTranslator extends PacketTranslator { @@ -48,7 +48,7 @@ public class BedrockCommandRequestTranslator extends PacketTranslator { @@ -40,7 +40,7 @@ public class BedrockTextTranslator extends PacketTranslator { public void translate(TextPacket packet, GeyserSession session) { String message = packet.getMessage().replaceAll("^\\.", "/").trim(); - if (MessageUtils.isTooLong(message, session)) { + if (MessageTranslator.isTooLong(message, session)) { return; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/chat/MessageTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/chat/MessageTranslator.java new file mode 100644 index 00000000..be01362f --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/chat/MessageTranslator.java @@ -0,0 +1,278 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.chat; + +import com.github.steveice10.mc.protocol.data.game.scoreboard.TeamColor; +import com.github.steveice10.mc.protocol.data.message.style.ChatColor; +import com.github.steveice10.mc.protocol.data.message.style.ChatFormat; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.renderer.TranslatableComponentRenderer; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import net.kyori.adventure.translation.TranslationRegistry; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.utils.LanguageUtils; + +import java.util.*; + +public class MessageTranslator { + + // These are used for handling the translations of the messages + private static final TranslationRegistry REGISTRY = new MinecraftTranslationRegistry(); + private static final TranslatableComponentRenderer RENDERER = TranslatableComponentRenderer.usingTranslationSource(REGISTRY); + + // Store team colors for player names + private static final Map TEAM_COLORS = new HashMap<>(); + + static { + TEAM_COLORS.put(TeamColor.BLACK, getColor(ChatColor.BLACK)); + TEAM_COLORS.put(TeamColor.DARK_BLUE, getColor(ChatColor.DARK_BLUE)); + TEAM_COLORS.put(TeamColor.DARK_GREEN, getColor(ChatColor.DARK_GREEN)); + TEAM_COLORS.put(TeamColor.DARK_AQUA, getColor(ChatColor.DARK_AQUA)); + TEAM_COLORS.put(TeamColor.DARK_RED, getColor(ChatColor.DARK_RED)); + TEAM_COLORS.put(TeamColor.DARK_PURPLE, getColor(ChatColor.DARK_PURPLE)); + TEAM_COLORS.put(TeamColor.GOLD, getColor(ChatColor.GOLD)); + TEAM_COLORS.put(TeamColor.GRAY, getColor(ChatColor.GRAY)); + TEAM_COLORS.put(TeamColor.DARK_GRAY, getColor(ChatColor.DARK_GRAY)); + TEAM_COLORS.put(TeamColor.BLUE, getColor(ChatColor.BLUE)); + TEAM_COLORS.put(TeamColor.GREEN, getColor(ChatColor.GREEN)); + TEAM_COLORS.put(TeamColor.AQUA, getColor(ChatColor.AQUA)); + TEAM_COLORS.put(TeamColor.RED, getColor(ChatColor.RED)); + TEAM_COLORS.put(TeamColor.LIGHT_PURPLE, getColor(ChatColor.LIGHT_PURPLE)); + TEAM_COLORS.put(TeamColor.YELLOW, getColor(ChatColor.YELLOW)); + TEAM_COLORS.put(TeamColor.WHITE, getColor(ChatColor.WHITE)); + TEAM_COLORS.put(TeamColor.OBFUSCATED, getFormat(ChatFormat.OBFUSCATED)); + TEAM_COLORS.put(TeamColor.BOLD, getFormat(ChatFormat.BOLD)); + TEAM_COLORS.put(TeamColor.STRIKETHROUGH, getFormat(ChatFormat.STRIKETHROUGH)); + TEAM_COLORS.put(TeamColor.ITALIC, getFormat(ChatFormat.ITALIC)); + } + + /** + * Convert a Java message to the legacy format ready for bedrock + * + * @param message Java message + * @param locale Locale to use for translation strings + * @return Parsed and formatted message for bedrock + */ + public static String convertMessage(String message, String locale) { + Component component = GsonComponentSerializer.gson().deserialize(message); + + // Get a Locale from the given locale string + Locale localeCode = Locale.forLanguageTag(locale.replace('_', '-')); + component = RENDERER.render(component, localeCode); + + return LegacyComponentSerializer.legacySection().serialize(component); + } + + public static String convertMessage(String message) { + return convertMessage(message, LanguageUtils.getDefaultLocale()); + } + + /** + * Verifies the message is valid JSON in case it's plaintext. Works around GsonComponentSeraializer not using lenient mode. + * See https://wiki.vg/Chat for messages sent in lenient mode, and for a description on leniency. + * + * @param message Potentially lenient JSON message + * @param locale Locale to use for translation strings + * @return Bedrock formatted message + */ + public static String convertMessageLenient(String message, String locale) { + if (isMessage(message)) { + return convertMessage(message, locale); + } else { + String convertedMessage = convertMessage(convertToJavaMessage(message), locale); + + // We have to do this since Adventure strips the starting reset character + if (message.startsWith(getColor(ChatColor.RESET))) { + convertedMessage = getColor(ChatColor.RESET) + convertedMessage; + } + + return convertedMessage; + } + } + + public static String convertMessageLenient(String message) { + return convertMessageLenient(message, LanguageUtils.getDefaultLocale()); + } + + /** + * Convert a Bedrock message string back to a format Java can understand + * + * @param message Message to convert + * @return The formatted JSON string + */ + public static String convertToJavaMessage(String message) { + Component component = LegacyComponentSerializer.legacySection().deserialize(message); + return GsonComponentSerializer.gson().serialize(component); + } + + /** + * Checks if the given text string is a JSON message + * + * @param text String to test + * @return True if its a valid message JSON string, false if not + */ + public static boolean isMessage(String text) { + if (text.trim().isEmpty()) { + return false; + } + + try { + GsonComponentSerializer.gson().deserialize(text); + } catch (Exception ex) { + return false; + } + + return true; + } + + /** + * Convert a {@link ChatColor} into a string for inserting into messages + * + * @param color {@link ChatColor} to convert + * @return The converted color string + */ + private static String getColor(String color) { + String base = "\u00a7"; + switch (color) { + case ChatColor.BLACK: + base += "0"; + break; + case ChatColor.DARK_BLUE: + base += "1"; + break; + case ChatColor.DARK_GREEN: + base += "2"; + break; + case ChatColor.DARK_AQUA: + base += "3"; + break; + case ChatColor.DARK_RED: + base += "4"; + break; + case ChatColor.DARK_PURPLE: + base += "5"; + break; + case ChatColor.GOLD: + base += "6"; + break; + case ChatColor.GRAY: + base += "7"; + break; + case ChatColor.DARK_GRAY: + base += "8"; + break; + case ChatColor.BLUE: + base += "9"; + break; + case ChatColor.GREEN: + base += "a"; + break; + case ChatColor.AQUA: + base += "b"; + break; + case ChatColor.RED: + base += "c"; + break; + case ChatColor.LIGHT_PURPLE: + base += "d"; + break; + case ChatColor.YELLOW: + base += "e"; + break; + case ChatColor.WHITE: + base += "f"; + break; + case ChatColor.RESET: + base += "r"; + break; + default: + return ""; + } + + return base; + } + + /** + * Convert a {@link ChatFormat} into a string for inserting into messages + * + * @param format {@link ChatFormat} to convert + * @return The converted chat formatting string + */ + private static String getFormat(ChatFormat format) { + StringBuilder str = new StringBuilder(); + String base = "\u00a7"; + switch (format) { + case OBFUSCATED: + base += "k"; + break; + case BOLD: + base += "l"; + break; + case STRIKETHROUGH: + base += "m"; + break; + case UNDERLINED: + base += "n"; + break; + case ITALIC: + base += "o"; + break; + default: + return ""; + } + + str.append(base); + + return str.toString(); + } + + /** + * Convert a team color to a chat color + * + * @param teamColor + * @return The chat color character + */ + public static String toChatColor(TeamColor teamColor) { + return TEAM_COLORS.getOrDefault(teamColor, ""); + } + + /** + * Checks if the given message is over 256 characters (Java edition server chat limit) and sends a message to the user if it is + * + * @param message Message to check + * @param session {@link GeyserSession} for the user + * @return True if the message is too long, false if not + */ + public static boolean isTooLong(String message, GeyserSession session) { + if (message.length() > 256) { + session.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.chat.too_long", session.getLocale(), message.length())); + return true; + } + + return false; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/chat/MinecraftTranslationRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/chat/MinecraftTranslationRegistry.java new file mode 100644 index 00000000..a23167ac --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/chat/MinecraftTranslationRegistry.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.chat; + +import net.kyori.adventure.key.Key; +import net.kyori.adventure.translation.TranslationRegistry; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.connector.utils.LocaleUtils; + +import java.text.MessageFormat; +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * This class is used for mapping a translation key with the already loaded Java locale data + * Used in MessageTranslator.java as part of the KyoriPowered/Adventure library + */ +public class MinecraftTranslationRegistry implements TranslationRegistry { + @Override + public @NonNull Key name() { + return Key.key("", ""); + } + + @Override + public @Nullable MessageFormat translate(@NonNull String key, @NonNull Locale locale) { + // Get the locale string + String localeString = LocaleUtils.getLocaleString(key, locale.toString()); + + // Replace the `%s` with numbered inserts `{0}` + Pattern p = Pattern.compile("%s"); + Matcher m = p.matcher(localeString); + StringBuffer sb = new StringBuffer(); + int i = 0; + while (m.find()) { + m.appendReplacement(sb, "{" + (i++) + "}"); + } + m.appendTail(sb); + + return new MessageFormat(sb.toString(), locale); + } + + @Override + public void defaultLocale(@NonNull Locale locale) { + + } + + @Override + public void register(@NonNull String key, @NonNull Locale locale, @NonNull MessageFormat format) { + + } + + @Override + public void unregister(@NonNull String key) { + + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java index 55db9a25..00c9138a 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java @@ -26,7 +26,6 @@ package org.geysermc.connector.network.translators.item; import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; -import com.github.steveice10.mc.protocol.data.message.MessageSerializer; import com.github.steveice10.opennbt.tag.builtin.*; import com.nukkitx.nbt.NbtList; import com.nukkitx.nbt.NbtMap; @@ -44,7 +43,7 @@ import org.geysermc.connector.network.translators.ItemRemapper; import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.utils.FileUtils; import org.geysermc.connector.utils.LanguageUtils; -import org.geysermc.connector.utils.MessageUtils; +import org.geysermc.connector.network.translators.chat.MessageTranslator; import org.reflections.Reflections; import java.util.*; @@ -385,26 +384,17 @@ public abstract class ItemTranslator { public static void translateDisplayProperties(GeyserSession session, CompoundTag tag) { if (tag != null) { CompoundTag display = tag.get("display"); - if (display != null && !display.isEmpty() && display.contains("Name")) { + if (display != null && display.contains("Name")) { String name = ((StringTag) display.get("Name")).getValue(); - // If its not a message convert it - if (!MessageUtils.isMessage(name)) { - Component component = LegacyComponentSerializer.legacySection().deserialize(name); - name = GsonComponentSerializer.gson().serialize(component); - } + // Get the translated name and prefix it with a reset char + name = MessageTranslator.convertMessageLenient(name, session.getLocale()); - // Check if its a message to translate - if (MessageUtils.isMessage(name)) { - // Get the translated name - name = MessageUtils.getTranslatedBedrockMessage(MessageSerializer.fromString(name), session.getLocale()); + // Add the new name tag + display.put(new StringTag("Name", name)); - // Add the new name tag - display.put(new StringTag("Name", name)); - - // Add to the new root tag - tag.put(display); - } + // Add to the new root tag + tag.put(display); } } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BasicItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BasicItemTranslator.java index 1d21bbfb..3fd9df8a 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BasicItemTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BasicItemTranslator.java @@ -37,7 +37,6 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.ItemRemapper; import org.geysermc.connector.network.translators.item.ItemEntry; import org.geysermc.connector.network.translators.item.NbtItemStackTranslator; -import org.geysermc.connector.utils.MessageUtils; import java.util.ArrayList; import java.util.List; @@ -108,7 +107,7 @@ public class BasicItemTranslator extends NbtItemStackTranslator { private String toBedrockMessage(StringTag tag) { String message = tag.getValue(); if (message == null) return null; - TextComponent component = (TextComponent) MessageUtils.phraseJavaMessage(message); + TextComponent component = (TextComponent) GsonComponentSerializer.gson().deserialize(message); String legacy = LegacyComponentSerializer.legacySection().serialize(component); if (hasFormatting(LegacyComponentSerializer.legacySection().deserialize(legacy))) { return "§r" + legacy; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BookPagesTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BookPagesTranslator.java index 41ee4fbc..294dd81e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BookPagesTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BookPagesTranslator.java @@ -33,7 +33,7 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.ItemRemapper; import org.geysermc.connector.network.translators.item.NbtItemStackTranslator; import org.geysermc.connector.network.translators.item.ItemEntry; -import org.geysermc.connector.utils.MessageUtils; +import org.geysermc.connector.network.translators.chat.MessageTranslator; import java.util.ArrayList; import java.util.List; @@ -56,7 +56,7 @@ public class BookPagesTranslator extends NbtItemStackTranslator { CompoundTag pageTag = new CompoundTag(""); pageTag.put(new StringTag("photoname", "")); - pageTag.put(new StringTag("text", MessageUtils.getBedrockMessageLenient(textTag.getValue()))); + pageTag.put(new StringTag("text", MessageTranslator.convertMessageLenient(textTag.getValue()))); pages.add(pageTag); } @@ -78,7 +78,7 @@ public class BookPagesTranslator extends NbtItemStackTranslator { CompoundTag pageTag = (CompoundTag) tag; StringTag textTag = pageTag.get("text"); - pages.add(new StringTag(MessageUtils.getJavaMessage(textTag.getValue()))); + pages.add(new StringTag(MessageTranslator.convertToJavaMessage(textTag.getValue()))); } itemTag.remove("pages"); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaChatTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaChatTranslator.java index 186aaf66..f5128ed6 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaChatTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaChatTranslator.java @@ -25,15 +25,12 @@ package org.geysermc.connector.network.translators.java; -import com.github.steveice10.mc.protocol.data.message.TranslationMessage; import com.github.steveice10.mc.protocol.packet.ingame.server.ServerChatPacket; import com.nukkitx.protocol.bedrock.packet.TextPacket; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.utils.MessageUtils; - -import java.util.List; +import org.geysermc.connector.network.translators.chat.MessageTranslator; @Translator(packet = ServerChatPacket.class) public class JavaChatTranslator extends PacketTranslator { @@ -59,21 +56,8 @@ public class JavaChatTranslator extends PacketTranslator { break; } - String locale = session.getLocale(); - - if (packet.getMessage() instanceof TranslationMessage) { - textPacket.setType(TextPacket.Type.TRANSLATION); - textPacket.setNeedsTranslation(true); - - List paramsTranslated = MessageUtils.getTranslationParams(((TranslationMessage) packet.getMessage()).getWith(), locale, packet.getMessage()); - textPacket.setParameters(paramsTranslated); - - textPacket.setMessage(MessageUtils.insertParams(MessageUtils.getTranslatedBedrockMessage(packet.getMessage(), locale, true, packet.getMessage()), paramsTranslated)); - } else { - textPacket.setNeedsTranslation(false); - - textPacket.setMessage(MessageUtils.getTranslatedBedrockMessage(packet.getMessage(), locale, false, packet.getMessage())); - } + textPacket.setNeedsTranslation(false); + textPacket.setMessage(MessageTranslator.convertMessage(packet.getMessage().toString(), session.getLocale())); session.sendUpstreamPacket(textPacket); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDisconnectPacket.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDisconnectPacket.java index f36da367..1945a8e1 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDisconnectPacket.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDisconnectPacket.java @@ -29,13 +29,13 @@ import com.github.steveice10.mc.protocol.packet.ingame.server.ServerDisconnectPa import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.utils.MessageUtils; +import org.geysermc.connector.network.translators.chat.MessageTranslator; @Translator(packet = ServerDisconnectPacket.class) public class JavaDisconnectPacket extends PacketTranslator { @Override public void translate(ServerDisconnectPacket packet, GeyserSession session) { - session.disconnect(MessageUtils.getTranslatedBedrockMessage(packet.getReason(), session.getLocale(), true)); + session.disconnect(MessageTranslator.convertMessage(packet.getReason().toString(), session.getLocale())); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginDisconnectTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginDisconnectTranslator.java index e7486c99..0a1cc3dd 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginDisconnectTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginDisconnectTranslator.java @@ -29,7 +29,7 @@ import com.github.steveice10.mc.protocol.packet.login.server.LoginDisconnectPack import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.utils.MessageUtils; +import org.geysermc.connector.network.translators.chat.MessageTranslator; @Translator(packet = LoginDisconnectPacket.class) public class JavaLoginDisconnectTranslator extends PacketTranslator { @@ -37,6 +37,6 @@ public class JavaLoginDisconnectTranslator extends PacketTranslator { SetTitlePacket titlePacket = new SetTitlePacket(); String locale = session.getLocale(); + String text; + if (packet.getTitle() == null) { + text = " "; + } else { + text = MessageTranslator.convertMessage(packet.getTitle().toString(), locale); + } + switch (packet.getAction()) { case TITLE: titlePacket.setType(SetTitlePacket.Type.TITLE); - titlePacket.setText(MessageUtils.getTranslatedBedrockMessage(packet.getTitle(), locale)); + titlePacket.setText(text); break; case SUBTITLE: titlePacket.setType(SetTitlePacket.Type.SUBTITLE); - titlePacket.setText(MessageUtils.getTranslatedBedrockMessage(packet.getTitle(), locale)); + titlePacket.setText(text); break; case CLEAR: case RESET: @@ -57,9 +64,10 @@ public class JavaTitleTranslator extends PacketTranslator { break; case ACTION_BAR: titlePacket.setType(SetTitlePacket.Type.ACTIONBAR); - titlePacket.setText(MessageUtils.getTranslatedBedrockMessage(packet.getTitle(), locale)); + titlePacket.setText(text); break; case TIMES: + titlePacket.setType(SetTitlePacket.Type.TIMES); titlePacket.setFadeInTime(packet.getFadeIn()); titlePacket.setFadeOutTime(packet.getFadeOut()); titlePacket.setStayTime(packet.getStay()); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaScoreboardObjectiveTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaScoreboardObjectiveTranslator.java index 31b9d95b..1996f696 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaScoreboardObjectiveTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaScoreboardObjectiveTranslator.java @@ -32,7 +32,7 @@ import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.scoreboard.Objective; import org.geysermc.connector.scoreboard.Scoreboard; import org.geysermc.connector.scoreboard.ScoreboardUpdater; -import org.geysermc.connector.utils.MessageUtils; +import org.geysermc.connector.network.translators.chat.MessageTranslator; import com.github.steveice10.mc.protocol.data.game.scoreboard.ObjectiveAction; import com.github.steveice10.mc.protocol.packet.ingame.server.scoreboard.ServerScoreboardObjectivePacket; @@ -54,7 +54,7 @@ public class JavaScoreboardObjectiveTranslator extends PacketTranslator { switch (packet.getAction()) { case CREATE: scoreboard.registerNewTeam(packet.getTeamName(), toPlayerSet(packet.getPlayers())) - .setName(MessageUtils.getBedrockMessage(packet.getDisplayName())) + .setName(MessageTranslator.convertMessage(packet.getDisplayName().toString())) .setColor(packet.getColor()) .setNameTagVisibility(packet.getNameTagVisibility()) - .setPrefix(MessageUtils.getTranslatedBedrockMessage(packet.getPrefix(), session.getLocale())) - .setSuffix(MessageUtils.getTranslatedBedrockMessage(packet.getSuffix(), session.getLocale())); + .setPrefix(MessageTranslator.convertMessage(packet.getPrefix().toString(), session.getLocale())) + .setSuffix(MessageTranslator.convertMessage(packet.getSuffix().toString(), session.getLocale())); break; case UPDATE: if (team == null) { @@ -74,11 +74,11 @@ public class JavaTeamTranslator extends PacketTranslator { return; } - team.setName(MessageUtils.getBedrockMessage(packet.getDisplayName())) + team.setName(MessageTranslator.convertMessage(packet.getDisplayName().toString())) .setColor(packet.getColor()) .setNameTagVisibility(packet.getNameTagVisibility()) - .setPrefix(MessageUtils.getTranslatedBedrockMessage(packet.getPrefix(), session.getLocale())) - .setSuffix(MessageUtils.getTranslatedBedrockMessage(packet.getSuffix(), session.getLocale())) + .setPrefix(MessageTranslator.convertMessage(packet.getPrefix().toString(), session.getLocale())) + .setSuffix(MessageTranslator.convertMessage(packet.getSuffix().toString(), session.getLocale())) .setUpdateType(UpdateType.UPDATE); break; case ADD_PLAYER: diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java index 2c10ded6..1fb08871 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java @@ -25,7 +25,6 @@ package org.geysermc.connector.network.translators.java.window; -import com.github.steveice10.mc.protocol.data.message.MessageSerializer; import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientCloseWindowPacket; import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerOpenWindowPacket; import org.geysermc.connector.inventory.Inventory; @@ -35,7 +34,7 @@ import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.network.translators.inventory.InventoryTranslator; import org.geysermc.connector.utils.InventoryUtils; import org.geysermc.connector.utils.LocaleUtils; -import org.geysermc.connector.utils.MessageUtils; +import org.geysermc.connector.network.translators.chat.MessageTranslator; @Translator(packet = ServerOpenWindowPacket.class) public class JavaOpenWindowTranslator extends PacketTranslator { @@ -57,8 +56,7 @@ public class JavaOpenWindowTranslator extends PacketTranslator - * The color names correspond to dye names, because of this we can't use {@link MessageUtils#getColor(String)}. + * The color names correspond to dye names, because of this we can't use {@link MessageTranslator#getColor(String)}. * * @param javaColor The dye color stored in the sign's Color tag. * @return A Bedrock Edition formatting code for valid dye colors, otherwise an empty string. */ - private static String getBedrockSignColor(String javaColor) { + private String getBedrockSignColor(String javaColor) { String base = "\u00a7"; switch (javaColor) { case "white": @@ -100,7 +99,12 @@ public class SignBlockEntityTranslator extends BlockEntityTranslator { for (int i = 0; i < 4; i++) { int currentLine = i + 1; String signLine = getOrDefault(tag.getValue().get("Text" + currentLine), ""); - signLine = MessageUtils.getBedrockMessage(MessageSerializer.fromString(signLine)); + signLine = MessageTranslator.convertMessageLenient(signLine); + + // Trim any trailing formatting codes + if (signLine.length() > 2 && signLine.toCharArray()[signLine.length() - 2] == '\u00a7') { + signLine = signLine.substring(0, signLine.length() - 2); + } // Check the character width on the sign to ensure there is no overflow that is usually hidden // to Java Edition clients but will appear to Bedrock clients @@ -124,6 +128,6 @@ public class SignBlockEntityTranslator extends BlockEntityTranslator { signText.append("\n"); } - builder.put("Text", MessageUtils.getBedrockMessage(MessageSerializer.fromString(signText.toString()))); + builder.put("Text", signText.toString()); } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java b/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java index 63255cfa..0b2b132a 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java @@ -159,7 +159,8 @@ public class FileUtils { } /** - * Calculate the SHA256 hash of the resource pack file + * Calculate the SHA256 hash of a file + * * @param file File to calculate the hash for * @return A byte[] representation of the hash */ @@ -175,6 +176,24 @@ public class FileUtils { return sha256; } + /** + * Calculate the SHA1 hash of a file + * + * @param file File to calculate the hash for + * @return A byte[] representation of the hash + */ + public static byte[] calculateSHA1(File file) { + byte[] sha1; + + try { + sha1 = MessageDigest.getInstance("SHA-1").digest(Files.readAllBytes(file.toPath())); + } catch (Exception e) { + throw new RuntimeException("Could not calculate pack hash", e); + } + + return sha1; + } + /** * Get the stored reflection data for a given path * diff --git a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java index dfde21b3..4e9e4b00 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java @@ -47,7 +47,7 @@ public class LocaleUtils { private static final Map ASSET_MAP = new HashMap<>(); - private static String smallestURL = ""; + private static VersionDownload clientJarInfo; static { // Create the locales folder @@ -87,9 +87,8 @@ public class LocaleUtils { // Get the client jar for use when downloading the en_us locale GeyserConnector.getInstance().getLogger().debug(GeyserConnector.JSON_MAPPER.writeValueAsString(versionInfo.getDownloads())); - VersionDownload download = versionInfo.getDownloads().get("client"); - GeyserConnector.getInstance().getLogger().debug(GeyserConnector.JSON_MAPPER.writeValueAsString(download)); - smallestURL = download.getUrl(); + clientJarInfo = versionInfo.getDownloads().get("client"); + GeyserConnector.getInstance().getLogger().debug(GeyserConnector.JSON_MAPPER.writeValueAsString(clientJarInfo)); // Get the assets list JsonNode assets = GeyserConnector.JSON_MAPPER.readTree(WebUtils.getBody(versionInfo.getAssetIndex().getUrl())).get("objects"); @@ -136,8 +135,28 @@ public class LocaleUtils { // Check if we have already downloaded the locale file if (localeFile.exists()) { - GeyserConnector.getInstance().getLogger().debug("Locale already downloaded: " + locale); - return; + String curHash = ""; + String targetHash = ""; + + if (locale.equals("en_us")) { + try { + Path hashFile = localeFile.getParentFile().toPath().resolve("en_us.hash"); + if (hashFile.toFile().exists()) { + curHash = String.join("", Files.readAllLines(hashFile)); + } + } catch (IOException ignored) { } + targetHash = clientJarInfo.getSha1(); + } else { + curHash = byteArrayToHexString(FileUtils.calculateSHA1(localeFile)); + targetHash = ASSET_MAP.get("minecraft/lang/" + locale + ".json").getHash(); + } + + if (!curHash.equals(targetHash)) { + GeyserConnector.getInstance().getLogger().debug("Locale out of date; re-downloading: " + locale); + } else { + GeyserConnector.getInstance().getLogger().debug("Locale already downloaded and up-to date: " + locale); + return; + } } // Create the en_us locale @@ -202,11 +221,11 @@ public class LocaleUtils { try { // Let the user know we are downloading the JAR GeyserConnector.getInstance().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.locale.download.en_us")); - GeyserConnector.getInstance().getLogger().debug("Download URL: " + smallestURL); + GeyserConnector.getInstance().getLogger().debug("Download URL: " + clientJarInfo.getUrl()); // Download the smallest JAR (client or server) Path tmpFilePath = GeyserConnector.getInstance().getBootstrap().getConfigFolder().resolve("tmp_locale.jar"); - WebUtils.downloadFile(smallestURL, tmpFilePath.toString()); + WebUtils.downloadFile(clientJarInfo.getUrl(), tmpFilePath.toString()); // Load in the JAR as a zip and extract the file ZipFile localeJar = new ZipFile(tmpFilePath.toString()); @@ -227,6 +246,9 @@ public class LocaleUtils { fileStream.close(); localeJar.close(); + // Store the latest jar hash + FileUtils.writeFile(localeFile.getParentFile().toPath().resolve("en_us.hash").toString(), clientJarInfo.getSha1().toCharArray()); + // Delete the nolonger needed client/server jar Files.delete(tmpFilePath); } catch (Exception e) { @@ -255,6 +277,20 @@ public class LocaleUtils { return localeStrings.getOrDefault(messageText, messageText); } + /** + * Convert a byte array into a hex string + * + * @param b Byte array to convert + * @return The hex representation of the given byte array + */ + private static String byteArrayToHexString(byte[] b) { + StringBuilder result = new StringBuilder(); + for (byte value : b) { + result.append(Integer.toString((value & 0xff) + 0x100, 16).substring(1)); + } + return result.toString(); + } + public static void init() { // no-op } diff --git a/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java b/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java deleted file mode 100644 index b5a2bfdc..00000000 --- a/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java +++ /dev/null @@ -1,494 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.utils; - -import com.github.steveice10.mc.protocol.data.game.scoreboard.TeamColor; -import com.github.steveice10.mc.protocol.data.message.Message; -import com.github.steveice10.mc.protocol.data.message.MessageSerializer; -import com.github.steveice10.mc.protocol.data.message.TextMessage; -import com.github.steveice10.mc.protocol.data.message.TranslationMessage; -import com.github.steveice10.mc.protocol.data.message.style.ChatColor; -import com.github.steveice10.mc.protocol.data.message.style.ChatFormat; -import com.github.steveice10.mc.protocol.data.message.style.MessageStyle; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; -import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; -import org.geysermc.connector.network.session.GeyserSession; - -import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class MessageUtils { - - private static final Map COLORS = new HashMap<>(); - private static final Map TEAM_COLORS = new HashMap<>(); - - static { - COLORS.put(ChatColor.BLACK, 0x000000); - COLORS.put(ChatColor.DARK_BLUE, 0x0000aa); - COLORS.put(ChatColor.DARK_GREEN, 0x00aa00); - COLORS.put(ChatColor.DARK_AQUA, 0x00aaaa); - COLORS.put(ChatColor.DARK_RED, 0xaa0000); - COLORS.put(ChatColor.DARK_PURPLE, 0xaa00aa); - COLORS.put(ChatColor.GOLD, 0xffaa00); - COLORS.put(ChatColor.GRAY, 0xaaaaaa); - COLORS.put(ChatColor.DARK_GRAY, 0x555555); - COLORS.put(ChatColor.BLUE, 0x5555ff); - COLORS.put(ChatColor.GREEN, 0x55ff55); - COLORS.put(ChatColor.AQUA, 0x55ffff); - COLORS.put(ChatColor.RED, 0xff5555); - COLORS.put(ChatColor.LIGHT_PURPLE, 0xff55ff); - COLORS.put(ChatColor.YELLOW, 0xffff55); - COLORS.put(ChatColor.WHITE, 0xffffff); - - TEAM_COLORS.put(TeamColor.BLACK, getColor(ChatColor.BLACK)); - TEAM_COLORS.put(TeamColor.DARK_BLUE, getColor(ChatColor.DARK_BLUE)); - TEAM_COLORS.put(TeamColor.DARK_GREEN, getColor(ChatColor.DARK_GREEN)); - TEAM_COLORS.put(TeamColor.DARK_AQUA, getColor(ChatColor.DARK_AQUA)); - TEAM_COLORS.put(TeamColor.DARK_RED, getColor(ChatColor.DARK_RED)); - TEAM_COLORS.put(TeamColor.DARK_PURPLE, getColor(ChatColor.DARK_PURPLE)); - TEAM_COLORS.put(TeamColor.GOLD, getColor(ChatColor.GOLD)); - TEAM_COLORS.put(TeamColor.GRAY, getColor(ChatColor.GRAY)); - TEAM_COLORS.put(TeamColor.DARK_GRAY, getColor(ChatColor.DARK_GRAY)); - TEAM_COLORS.put(TeamColor.BLUE, getColor(ChatColor.BLUE)); - TEAM_COLORS.put(TeamColor.GREEN, getColor(ChatColor.GREEN)); - TEAM_COLORS.put(TeamColor.AQUA, getColor(ChatColor.AQUA)); - TEAM_COLORS.put(TeamColor.RED, getColor(ChatColor.RED)); - TEAM_COLORS.put(TeamColor.LIGHT_PURPLE, getColor(ChatColor.LIGHT_PURPLE)); - TEAM_COLORS.put(TeamColor.YELLOW, getColor(ChatColor.YELLOW)); - TEAM_COLORS.put(TeamColor.WHITE, getColor(ChatColor.WHITE)); - TEAM_COLORS.put(TeamColor.OBFUSCATED, getFormat(Collections.singletonList(ChatFormat.OBFUSCATED))); - TEAM_COLORS.put(TeamColor.BOLD, getFormat(Collections.singletonList(ChatFormat.BOLD))); - TEAM_COLORS.put(TeamColor.STRIKETHROUGH, getFormat(Collections.singletonList(ChatFormat.STRIKETHROUGH))); - TEAM_COLORS.put(TeamColor.ITALIC, getFormat(Collections.singletonList(ChatFormat.ITALIC))); - } - - /** - * Recursively parse each message from a list for usage in a {@link TranslationMessage} - * - * @param messages A {@link List} of {@link Message} to parse - * @param locale A locale loaded to get the message for - * @param parent A {@link Message} to use as the parent (can be null) - * @return the translation parameters - */ - public static List getTranslationParams(List messages, String locale, Message parent) { - List strings = new ArrayList<>(); - for (Message message : messages) { - message = fixMessageStyle(message, parent); - - if (message instanceof TranslationMessage) { - TranslationMessage translation = (TranslationMessage) message; - - if (locale == null) { - String builder = "%" + translation.getKey(); - strings.add(builder); - } - - // Collect all params and add format corrections to the end of them - List furtherParams = new ArrayList<>(); - for (String param : getTranslationParams(translation.getWith(), locale, message)) { - String newParam = param; - if (parent.getStyle().getFormats().size() != 0) { - newParam += getFormat(parent.getStyle().getFormats()); - } - if (parent.getStyle().getColor() != ChatColor.NONE) { - newParam += getColor(parent.getStyle().getColor()); - } - - furtherParams.add(newParam); - } - - if (locale != null) { - String builder = getFormat(message.getStyle().getFormats()) + - getColor(message.getStyle().getColor()); - builder += insertParams(LocaleUtils.getLocaleString(translation.getKey(), locale), furtherParams); - strings.add(builder); - } else { - String format = getFormat(message.getStyle().getFormats()) + - getColor(message.getStyle().getColor()); - for (String param : furtherParams) { - strings.add(format + param); - } - } - } else { - String builder = getFormat(message.getStyle().getFormats()) + - getColor(message.getStyle().getColor()); - builder += getTranslatedBedrockMessage(message, locale, false, parent); - strings.add(builder); - } - } - - return strings; - } - - public static String getTranslatedBedrockMessage(Message message, String locale) { - return getTranslatedBedrockMessage(message, locale, true); - } - - public static String getTranslatedBedrockMessage(Message message, String locale, boolean shouldTranslate) { - return getTranslatedBedrockMessage(message, locale, shouldTranslate, null); - } - - /** - * Translate a given {@link TranslationMessage} to the given locale - * - * @param message The {@link Message} to send - * @param locale the locale - * @param shouldTranslate if the message should be translated - * @param parent the parent message - * @return the given translation message translated from the given locale - */ - public static String getTranslatedBedrockMessage(Message message, String locale, boolean shouldTranslate, Message parent) { - JsonParser parser = new JsonParser(); - if (isMessage(message.toString())) { - JsonObject object = parser.parse(message.toString()).getAsJsonObject(); - message = MessageSerializer.fromJson(object); - } - - message = fixMessageStyle(message, parent); - - String messageText = (message instanceof TranslationMessage) ? ((TranslationMessage) message).getKey() : ((TextMessage) message).getText(); - if (locale != null && shouldTranslate) { - messageText = LocaleUtils.getLocaleString(messageText, locale); - } - - StringBuilder builder = new StringBuilder(); - builder.append(getFormat(message.getStyle().getFormats())); - builder.append(getColor(message.getStyle().getColor())); - builder.append(messageText); - - for (Message msg : message.getExtra()) { - builder.append(getFormat(msg.getStyle().getFormats())); - builder.append(getColor(msg.getStyle().getColor())); - if (!(msg.toString() == null)) { - boolean isTranslationMessage = (msg instanceof TranslationMessage); - String extraText = ""; - - if (isTranslationMessage) { - List paramsTranslated = getTranslationParams(((TranslationMessage) msg).getWith(), locale, message); - extraText = insertParams(getTranslatedBedrockMessage(msg, locale, isTranslationMessage, message), paramsTranslated); - } else { - extraText = getTranslatedBedrockMessage(msg, locale, isTranslationMessage, message); - } - - builder.append(extraText); - builder.append("\u00a7r"); - } - } - - return builder.toString(); - } - - /** - * If the passed {@link Message} color or format are empty then copy from parent - * - * @param message {@link Message} to update - * @param parent Parent {@link Message} for style - * @return The updated {@link Message} - */ - private static Message fixMessageStyle(Message message, Message parent) { - if (parent == null) { - return message; - } - MessageStyle.Builder styleBuilder = message.getStyle().toBuilder(); - - // Copy color from parent - if (message.getStyle().getColor() == ChatColor.NONE) { - styleBuilder.color(parent.getStyle().getColor()); - } - - // Copy formatting from parent - if (message.getStyle().getFormats().size() == 0) { - styleBuilder.formats(parent.getStyle().getFormats()); - } - - return message.toBuilder().style(styleBuilder.build()).build(); - } - - public static String getBedrockMessage(Message message) { - if (isMessage(((TextMessage) message).getText())) { - return getBedrockMessage(((TextMessage) message).getText()); - } else { - return getBedrockMessage(MessageSerializer.toJsonString(message)); - } - } - - /** - * Verifies the message is valid JSON in case it's plaintext. Works around GsonComponentSeraializer not using lenient mode. - * See https://wiki.vg/Chat for messages sent in lenient mode, and for a description on leniency. - * - * @param message Potentially lenient JSON message - * @return Bedrock formatted message - */ - public static String getBedrockMessageLenient(String message) { - if (isMessage(message)) { - return getBedrockMessage(message); - } else { - final JsonObject obj = new JsonObject(); - obj.addProperty("text", message); - return getBedrockMessage(obj.toString()); - } - } - - public static String getBedrockMessage(String message) { - Component component = phraseJavaMessage(message); - return LegacyComponentSerializer.legacySection().serialize(component); - } - - public static Component phraseJavaMessage(String message) { - return GsonComponentSerializer.gson().deserialize(message); - } - - public static String getJavaMessage(String message) { - Component component = LegacyComponentSerializer.legacySection().deserialize(message); - return GsonComponentSerializer.gson().serialize(component); - } - - /** - * Inserts the given parameters into the given message both in sequence and as requested - * - * @param message Message containing possible parameter replacement strings - * @param params A list of parameter strings - * @return Parsed message with all params inserted as needed - */ - public static String insertParams(String message, List params) { - String newMessage = message; - - Pattern p = Pattern.compile("%([1-9])\\$s"); - Matcher m = p.matcher(message); - while (m.find()) { - try { - newMessage = newMessage.replaceFirst("%" + m.group(1) + "\\$s", params.get(Integer.parseInt(m.group(1)) - 1)); - } catch (Exception e) { - // Couldn't find the param to replace - } - } - - for (String text : params) { - newMessage = newMessage.replaceFirst("%s", text.replaceAll("%s", "%r")); - } - - newMessage = newMessage.replaceAll("%r", "MISSING!"); - - return newMessage; - } - - /** - * Convert a ChatColor into a string for inserting into messages - * - * @param color ChatColor to convert - * @return The converted color string - */ - private static String getColor(String color) { - String base = "\u00a7"; - switch (color) { - case ChatColor.BLACK: - base += "0"; - break; - case ChatColor.DARK_BLUE: - base += "1"; - break; - case ChatColor.DARK_GREEN: - base += "2"; - break; - case ChatColor.DARK_AQUA: - base += "3"; - break; - case ChatColor.DARK_RED: - base += "4"; - break; - case ChatColor.DARK_PURPLE: - base += "5"; - break; - case ChatColor.GOLD: - base += "6"; - break; - case ChatColor.GRAY: - base += "7"; - break; - case ChatColor.DARK_GRAY: - base += "8"; - break; - case ChatColor.BLUE: - base += "9"; - break; - case ChatColor.GREEN: - base += "a"; - break; - case ChatColor.AQUA: - base += "b"; - break; - case ChatColor.RED: - base += "c"; - break; - case ChatColor.LIGHT_PURPLE: - base += "d"; - break; - case ChatColor.YELLOW: - base += "e"; - break; - case ChatColor.WHITE: - base += "f"; - break; - case ChatColor.RESET: - //case NONE: - base += "r"; - break; - case "": // To stop recursion - return ""; - default: - return getClosestColor(color); - } - - return base; - } - - /** - * Based on https://github.com/ViaVersion/ViaBackwards/blob/master/core/src/main/java/nl/matsv/viabackwards/protocol/protocol1_15_2to1_16/chat/TranslatableRewriter1_16.java - * - * @param color A color string - * @return The closest color to that string - */ - private static String getClosestColor(String color) { - if (!color.startsWith("#")) { - return ""; - } - - int rgb = Integer.parseInt(color.substring(1), 16); - int r = (rgb >> 16) & 0xFF; - int g = (rgb >> 8) & 0xFF; - int b = rgb & 0xFF; - - String closest = null; - int smallestDiff = 0; - - for (Map.Entry testColor : COLORS.entrySet()) { - if (testColor.getValue() == rgb) { - closest = testColor.getKey(); - break; - } - - int testR = (testColor.getValue() >> 16) & 0xFF; - int testG = (testColor.getValue() >> 8) & 0xFF; - int testB = testColor.getValue() & 0xFF; - - // Check by the greatest diff of the 3 values - int rAverage = (testR + r) / 2; - int rDiff = testR - r; - int gDiff = testG - g; - int bDiff = testB - b; - int diff = ((2 + (rAverage >> 8)) * rDiff * rDiff) - + (4 * gDiff * gDiff) - + ((2 + ((255 - rAverage) >> 8)) * bDiff * bDiff); - if (closest == null || diff < smallestDiff) { - closest = testColor.getKey(); - smallestDiff = diff; - } - } - - return getColor(closest); - } - - /** - * Convert a list of ChatFormats into a string for inserting into messages - * - * @param formats ChatFormats to convert - * @return The converted chat formatting string - */ - private static String getFormat(List formats) { - StringBuilder str = new StringBuilder(); - for (ChatFormat cf : formats) { - String base = "\u00a7"; - switch (cf) { - case OBFUSCATED: - base += "k"; - break; - case BOLD: - base += "l"; - break; - case STRIKETHROUGH: - base += "m"; - break; - case UNDERLINED: - base += "n"; - break; - case ITALIC: - base += "o"; - break; - default: - return ""; - } - - str.append(base); - } - - return str.toString(); - } - - /** - * Checks if the given text string is a json message - * - * @param text String to test - * @return True if its a valid message json string, false if not - */ - public static boolean isMessage(String text) { - JsonParser parser = new JsonParser(); - try { - JsonObject object = parser.parse(text).getAsJsonObject(); - try { - MessageSerializer.fromJson(object); - } catch (Exception ex) { - return false; - } - } catch (Exception ex) { - return false; - } - return true; - } - - public static String toChatColor(TeamColor teamColor) { - return TEAM_COLORS.getOrDefault(teamColor, ""); - } - - /** - * Checks if the given message is over 256 characters (Java edition server chat limit) and sends a message to the user if it is - * - * @param message Message to check - * @param session GeyserSession for the user - * @return True if the message is too long, false if not - */ - public static boolean isTooLong(String message, GeyserSession session) { - if (message.length() > 256) { - session.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.chat.too_long", session.getLocale(), message.length())); - return true; - } - - return false; - } -} diff --git a/connector/src/test/java/org/geysermc/connector/network/translators/chat/MessageTranslatorTest.java b/connector/src/test/java/org/geysermc/connector/network/translators/chat/MessageTranslatorTest.java new file mode 100644 index 00000000..5d52c79b --- /dev/null +++ b/connector/src/test/java/org/geysermc/connector/network/translators/chat/MessageTranslatorTest.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.chat; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +public class MessageTranslatorTest { + + private Map messages = new HashMap<>(); + + @Before + public void setUp() throws Exception { + messages.put("{\"text\":\"\",\"extra\":[{\"text\":\"DoctorMad9952 joined the game\",\"color\":\"yellow\"}]}", + "§eDoctorMad9952 joined the game"); + + messages.put("{\"text\":\"\",\"extra\":[\"Plugins (3): \",{\"text\":\"WorldEdit\",\"color\":\"green\"},{\"text\":\", \",\"color\":\"white\"},{\"text\":\"ViaVersion\",\"color\":\"green\"},{\"text\":\", \",\"color\":\"white\"},{\"text\":\"Geyser-Spigot\",\"color\":\"green\"}]}", + "Plugins (3): §aWorldEdit§f, §aViaVersion§f, §aGeyser-Spigot"); + + // RGB downgrade test + messages.put("{\"extra\":[{\"text\":\" \"},{\"color\":\"gold\",\"text\":\"The \"},{\"color\":\"#E14248\",\"obfuscated\":true,\"text\":\"||\"},{\"color\":\"#3AA9FF\",\"bold\":true,\"text\":\"CubeCraft\"},{\"color\":\"#E14248\",\"obfuscated\":true,\"text\":\"||\"},{\"color\":\"gold\",\"text\":\" Network \"},{\"color\":\"green\",\"text\":\"[1.8/1.9+]\\n \"},{\"color\":\"#f5e342\",\"text\":\"✦ \"},{\"color\":\"#b042f5\",\"bold\":true,\"text\":\"N\"},{\"color\":\"#c142f5\",\"bold\":true,\"text\":\"E\"},{\"color\":\"#d342f5\",\"bold\":true,\"text\":\"W\"},{\"color\":\"#e442f5\",\"bold\":true,\"text\":\":\"},{\"color\":\"#f542f5\",\"bold\":true,\"text\":\" \"},{\"color\":\"#bcf542\",\"bold\":true,\"text\":\"A\"},{\"color\":\"#acee3f\",\"bold\":true,\"text\":\"M\"},{\"color\":\"#9ce73c\",\"bold\":true,\"text\":\"O\"},{\"color\":\"#8ce039\",\"bold\":true,\"text\":\"N\"},{\"color\":\"#7cd936\",\"bold\":true,\"text\":\"G\"},{\"color\":\"#6cd233\",\"bold\":true,\"text\":\" \"},{\"color\":\"#5ccb30\",\"bold\":true,\"text\":\"S\"},{\"color\":\"#4cc42d\",\"bold\":true,\"text\":\"L\"},{\"color\":\"#3cbd2a\",\"bold\":true,\"text\":\"I\"},{\"color\":\"#2cb627\",\"bold\":true,\"text\":\"M\"},{\"color\":\"#1caf24\",\"bold\":true,\"text\":\"E\"},{\"color\":\"#0ca821\",\"bold\":true,\"text\":\"S\"},{\"color\":\"#f5e342\",\"text\":\" \"},{\"color\":\"#6d7c87\",\"text\":\"(kinda sus) \"},{\"color\":\"#f5e342\",\"text\":\"✦\"}],\"text\":\"\"}", + " §6The §c§k||§r§3§lCubeCraft§r§c§k||§r§6 Network §a[1.8/1.9+]\n" + + " §e✦ §d§lN§r§d§lE§r§d§lW§r§d§l:§r§d§l §r§e§lA§r§e§lM§r§a§lO§r§a§lN§r§a§lG§r§a§l §r§a§lS§r§a§lL§r§2§lI§r§2§lM§r§2§lE§r§2§lS§r§e §8(kinda sus) §e✦"); + } + + @Test + public void convertMessage() { + for (Map.Entry entry : messages.entrySet()) { + String bedrockMessage = MessageTranslator.convertMessage(entry.getKey(), "en_US"); + Assert.assertEquals("Translation of messages is incorrect", bedrockMessage, entry.getValue()); + } + } + + @Test + public void convertMessageLenient() { + Assert.assertEquals("All newline message is not handled properly", "\n\n\n\n", MessageTranslator.convertMessageLenient("\n\n\n\n")); + Assert.assertEquals("Empty message is not handled properly", "", MessageTranslator.convertMessageLenient("")); + Assert.assertEquals("Reset before message is not handled properly", "§r§eGame Selector", MessageTranslator.convertMessageLenient("§r§eGame Selector")); + } +} From 123b074cc76785135e9b374706791efb1cc41bb9 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Tue, 17 Nov 2020 12:03:12 -0500 Subject: [PATCH 115/205] Update to Bedrock 1.16.100 (#1552) * Initial work on 1.16.100 - currently crashes the client * Update runtime item states * Use new Bedrock runtime IDs Bedrock now hardcodes block runtime IDs in alphabetical order of the identifiers. This commit updates Geyser to accomodate. - Remove runtime_block_states.dat and replace it with blockpalette.nbt - Calculate the block runtime ID based on the order of the block palette - Separate BlockTranslator.AIR into Bedrock and Java values - Update the second layer of chunks to use air when not waterlogged - Don't send item palette for now, as that's what crashes the game (will look into for v415) - Other misc. changes * Improve second layer chunk translation * v415 support - Add a message warning people they are on a beta version of Geyser * Update to protocol v417 There are still some mappings changes that need to be gone through. * Update runtime item states and clean up item frames * Future-proof enchanment table * Update for v418 * Update to v419 * Apply proper air ID to waterlogged chunk layer * Fix missing import * Remove beta warning * Update mappings * Manually patch runtime_item_states and send the ITEMS registry * Update README * Disable grindstone and smithing inventories (since they're broken) * Use artifactory jenkins plugin (#1548) * Use artifactory jenkins plugin * Bump version to 1.2.0-SNAPSHOT Co-authored-by: SupremeMortal <6178101+SupremeMortal@users.noreply.github.com> --- Jenkinsfile | 23 +- README.md | 2 +- bootstrap/bungeecord/pom.xml | 6 +- bootstrap/pom.xml | 5 +- bootstrap/spigot/pom.xml | 6 +- .../world/GeyserSpigotWorldManager.java | 6 +- bootstrap/sponge/pom.xml | 6 +- bootstrap/standalone/pom.xml | 6 +- bootstrap/velocity/pom.xml | 6 +- common/pom.xml | 5 +- connector/pom.xml | 11 +- .../connector/entity/ItemFrameEntity.java | 10 +- .../connector/network/BedrockProtocol.java | 6 +- .../network/session/GeyserSession.java | 2 - .../network/session/cache/ChunkCache.java | 6 +- .../BedrockBlockPickRequestTranslator.java | 2 +- ...BedrockInventoryTransactionTranslator.java | 10 +- .../EnchantmentInventoryTranslator.java | 9 +- .../inventory/InventoryTranslator.java | 4 +- .../translators/item/ItemRegistry.java | 45 +- .../translators/nbt/CrossbowTranslator.java | 2 +- .../nbt/ShulkerBoxItemTranslator.java | 2 +- .../java/world/JavaExplosionTranslator.java | 2 +- .../translators/world/WorldManager.java | 10 +- .../world/block/BlockTranslator.java | 85 +- .../translators/world/chunk/BlockStorage.java | 3 +- .../geysermc/connector/utils/ChunkUtils.java | 23 +- .../main/resources/bedrock/blockpalette.nbt | Bin 0 -> 1160789 bytes .../resources/bedrock/creative_items.json | 3503 +++++----- .../resources/bedrock/legacy_block_ids.json | 555 -- .../resources/bedrock/legacy_item_ids.json | 255 - .../bedrock/runtime_block_states.dat | Bin 1063028 -> 0 bytes .../{items.json => runtime_item_states.json} | 5852 +++++++++-------- connector/src/main/resources/mappings | 2 +- pom.xml | 15 +- 35 files changed, 5019 insertions(+), 5466 deletions(-) create mode 100644 connector/src/main/resources/bedrock/blockpalette.nbt delete mode 100644 connector/src/main/resources/bedrock/legacy_block_ids.json delete mode 100644 connector/src/main/resources/bedrock/legacy_item_ids.json delete mode 100644 connector/src/main/resources/bedrock/runtime_block_states.dat rename connector/src/main/resources/bedrock/{items.json => runtime_item_states.json} (84%) diff --git a/Jenkinsfile b/Jenkinsfile index 1a93391d..96b189f2 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -24,8 +24,29 @@ pipeline { when { branch "master" } + steps { - sh 'mvn javadoc:jar source:jar deploy -DskipTests' + rtMavenDeployer( + id: "maven-deployer", + serverId: "opencollab-artifactory", + releaseRepo: "maven-releases", + snapshotRepo: "maven-snapshots" + ) + rtMavenResolver( + id: "maven-resolver", + serverId: "opencollab-artifactory", + releaseRepo: "release", + snapshotRepo: "snapshot" + ) + rtMavenRun( + pom: 'pom.xml', + goals: 'javadoc:jar source:jar install -DskipTests', + deployerId: "maven-deployer", + resolverId: "maven-resolver" + ) + rtPublishBuildInfo( + serverId: "opencollab-artifactory" + ) } } } diff --git a/README.md b/README.md index e2efc5b4..ab1ff24a 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have now joined us here! -### Currently supporting Minecraft Bedrock v1.16.x and Minecraft Java v1.16.4. +### Currently supporting Minecraft Bedrock v1.16.100 and Minecraft Java v1.16.4. ## Setting Up Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set up Geyser. diff --git a/bootstrap/bungeecord/pom.xml b/bootstrap/bungeecord/pom.xml index 44b28e93..124967b0 100644 --- a/bootstrap/bungeecord/pom.xml +++ b/bootstrap/bungeecord/pom.xml @@ -6,15 +6,15 @@ org.geysermc bootstrap-parent - 1.1.0 - ../ + 1.2.0-SNAPSHOT bootstrap-bungeecord + org.geysermc connector - 1.1.0 + 1.2.0-SNAPSHOT compile diff --git a/bootstrap/pom.xml b/bootstrap/pom.xml index d9bac67d..a5ad53cb 100644 --- a/bootstrap/pom.xml +++ b/bootstrap/pom.xml @@ -6,12 +6,11 @@ org.geysermc geyser-parent - parent - ../ + 1.2.0-SNAPSHOT bootstrap-parent - 1.1.0 pom + spigot-public diff --git a/bootstrap/spigot/pom.xml b/bootstrap/spigot/pom.xml index 067b446a..f3b9cf88 100644 --- a/bootstrap/spigot/pom.xml +++ b/bootstrap/spigot/pom.xml @@ -6,15 +6,15 @@ org.geysermc bootstrap-parent - 1.1.0 - ../ + 1.2.0-SNAPSHOT bootstrap-spigot + org.geysermc connector - 1.1.0 + 1.2.0-SNAPSHOT compile diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java index 28b2da3a..3493fc25 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java @@ -119,7 +119,7 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { if ((this.isLegacy && !this.isViaVersion) || session.getPlayerEntity() == null || (bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) { - return BlockTranslator.AIR; + return BlockTranslator.JAVA_AIR_ID; } World world = bukkitPlayer.getWorld(); if (isLegacy) { @@ -136,7 +136,7 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { BlockStorage storage = Via.getManager().getConnection(bukkitPlayer.getUniqueId()).get(BlockStorage.class); return getLegacyBlock(storage, bukkitPlayer.getWorld(), x, y, z); } else { - return BlockTranslator.AIR; + return BlockTranslator.JAVA_AIR_ID; } } @@ -190,7 +190,7 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { for (int blockZ = 0; blockZ < 16; blockZ++) { for (int blockX = 0; blockX < 16; blockX++) { Block block = world.getBlockAt((x << 4) + blockX, (y << 4) + blockY, (z << 4) + blockZ); - int id = BlockTranslator.getJavaIdBlockMap().getOrDefault(block.getBlockData().getAsString(), BlockTranslator.AIR); + int id = BlockTranslator.getJavaIdBlockMap().getOrDefault(block.getBlockData().getAsString(), BlockTranslator.JAVA_AIR_ID); chunk.set(blockX, blockY, blockZ, id); } } diff --git a/bootstrap/sponge/pom.xml b/bootstrap/sponge/pom.xml index 132f3817..e6ce8f85 100644 --- a/bootstrap/sponge/pom.xml +++ b/bootstrap/sponge/pom.xml @@ -6,15 +6,15 @@ org.geysermc bootstrap-parent - 1.1.0 - ../ + 1.2.0-SNAPSHOT bootstrap-sponge + org.geysermc connector - 1.1.0 + 1.2.0-SNAPSHOT compile diff --git a/bootstrap/standalone/pom.xml b/bootstrap/standalone/pom.xml index 07458f73..831239f6 100644 --- a/bootstrap/standalone/pom.xml +++ b/bootstrap/standalone/pom.xml @@ -6,15 +6,15 @@ org.geysermc bootstrap-parent - 1.1.0 - ../ + 1.2.0-SNAPSHOT bootstrap-standalone + org.geysermc connector - 1.1.0 + 1.2.0-SNAPSHOT compile diff --git a/bootstrap/velocity/pom.xml b/bootstrap/velocity/pom.xml index babb9a3e..86de99ba 100644 --- a/bootstrap/velocity/pom.xml +++ b/bootstrap/velocity/pom.xml @@ -6,15 +6,15 @@ org.geysermc bootstrap-parent - 1.1.0 - ../ + 1.2.0-SNAPSHOT bootstrap-velocity + org.geysermc connector - 1.1.0 + 1.2.0-SNAPSHOT compile diff --git a/common/pom.xml b/common/pom.xml index 85dde12c..32c4b187 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -6,11 +6,10 @@ org.geysermc geyser-parent - parent - ../ + 1.2.0-SNAPSHOT common - 1.1.0 + com.google.code.gson diff --git a/connector/pom.xml b/connector/pom.xml index db267a71..7c44ddfd 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -6,16 +6,15 @@ org.geysermc geyser-parent - parent - ../ + 1.2.0-SNAPSHOT connector - 1.1.0 + org.geysermc common - 1.1.0 + 1.2.0-SNAPSHOT compile @@ -32,8 +31,8 @@ com.github.CloudburstMC.Protocol - bedrock-v408 - 02f46a8700 + bedrock-v419 + ce59d39118 compile diff --git a/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java b/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java index 501c7e46..62fe3afe 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java @@ -69,7 +69,6 @@ public class ItemFrameEntity extends Entity { public ItemFrameEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation, HangingDirection direction) { super(entityId, geyserId, entityType, position, motion, rotation); - NbtMapBuilder builder = NbtMap.builder(); NbtMapBuilder blockBuilder = NbtMap.builder() .putString("name", "minecraft:frame") .putInt("version", BlockTranslator.getBlockStateVersion()); @@ -77,9 +76,7 @@ public class ItemFrameEntity extends Entity { .putInt("facing_direction", direction.ordinal()) .putByte("item_frame_map_bit", (byte) 0) .build()); - builder.put("block", blockBuilder.build()); - builder.putShort("id", (short) 199); - bedrockRuntimeId = BlockTranslator.getItemFrame(builder.build()); + bedrockRuntimeId = BlockTranslator.getItemFrame(blockBuilder.build()); bedrockPosition = Vector3i.from(position.getFloorX(), position.getFloorY(), position.getFloorZ()); } @@ -101,7 +98,7 @@ public class ItemFrameEntity extends Entity { ItemEntry itemEntry = ItemRegistry.getItem((ItemStack) entityMetadata.getValue()); NbtMapBuilder builder = NbtMap.builder(); - String blockName = ItemRegistry.getBedrockIdentifer(itemEntry); + String blockName = ItemRegistry.getBedrockIdentifier(itemEntry); builder.putByte("Count", (byte) itemData.getCount()); if (itemData.getTag() != null) { @@ -141,8 +138,7 @@ public class ItemFrameEntity extends Entity { UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket(); updateBlockPacket.setDataLayer(0); updateBlockPacket.setBlockPosition(bedrockPosition); - // TODO 1.16.100 set to BEDROCK_AIR - updateBlockPacket.setRuntimeId(0); + updateBlockPacket.setRuntimeId(BlockTranslator.BEDROCK_AIR_ID); updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.PRIORITY); updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NETWORK); updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NEIGHBORS); diff --git a/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java b/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java index 5d4462b4..85043d37 100644 --- a/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java +++ b/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java @@ -26,8 +26,7 @@ package org.geysermc.connector.network; import com.nukkitx.protocol.bedrock.BedrockPacketCodec; -import com.nukkitx.protocol.bedrock.v407.Bedrock_v407; -import com.nukkitx.protocol.bedrock.v408.Bedrock_v408; +import com.nukkitx.protocol.bedrock.v419.Bedrock_v419; import java.util.ArrayList; import java.util.List; @@ -40,14 +39,13 @@ public class BedrockProtocol { * Default Bedrock codec that should act as a fallback. Should represent the latest available * release of the game that Geyser supports. */ - public static final BedrockPacketCodec DEFAULT_BEDROCK_CODEC = Bedrock_v408.V408_CODEC; + public static final BedrockPacketCodec DEFAULT_BEDROCK_CODEC = Bedrock_v419.V419_CODEC; /** * A list of all supported Bedrock versions that can join Geyser */ public static final List SUPPORTED_BEDROCK_CODECS = new ArrayList<>(); static { - SUPPORTED_BEDROCK_CODECS.add(Bedrock_v407.V407_CODEC); SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC); } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 00b48a56..b8722122 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -75,7 +75,6 @@ import org.geysermc.connector.network.translators.EntityIdentifierRegistry; import org.geysermc.connector.network.translators.PacketTranslatorRegistry; import org.geysermc.connector.network.translators.inventory.EnchantmentInventoryTranslator; import org.geysermc.connector.network.translators.item.ItemRegistry; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.utils.*; import org.geysermc.floodgate.util.BedrockData; import org.geysermc.floodgate.util.EncryptionUtil; @@ -674,7 +673,6 @@ public class GeyserSession implements CommandSender { // startGamePacket.setCurrentTick(0); startGamePacket.setEnchantmentSeed(0); startGamePacket.setMultiplayerCorrelationId(""); - startGamePacket.setBlockPalette(BlockTranslator.BLOCKS); startGamePacket.setItemEntries(ItemRegistry.ITEMS); startGamePacket.setVanillaVersion("*"); startGamePacket.setAuthoritativeMovementMode(AuthoritativeMovementMode.CLIENT); diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java index 7bf84b8d..cbf3721f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java @@ -94,12 +94,12 @@ public class ChunkCache { public int getBlockAt(int x, int y, int z) { if (!cache) { - return BlockTranslator.AIR; + return BlockTranslator.JAVA_AIR_ID; } Column column = this.getChunk(x >> 4, z >> 4); if (column == null) { - return BlockTranslator.AIR; + return BlockTranslator.JAVA_AIR_ID; } Chunk chunk = column.getChunks()[y >> 4]; @@ -107,7 +107,7 @@ public class ChunkCache { return chunk.get(x & 0xF, y & 0xF, z & 0xF); } - return BlockTranslator.AIR; + return BlockTranslator.JAVA_AIR_ID; } public void removeChunk(int chunkX, int chunkZ) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestTranslator.java index 023a83af..3e40ddd6 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestTranslator.java @@ -42,7 +42,7 @@ public class BedrockBlockPickRequestTranslator extends PacketTranslator> itemEntriesType = new TypeReference>() { }; @@ -104,8 +110,13 @@ public class ItemRegistry { throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.toolbox.fail.runtime_bedrock"), e); } + int lodestoneCompassId = 0; + for (JsonNode entry : itemEntries) { ITEMS.add(new StartGamePacket.ItemEntry(entry.get("name").textValue(), (short) entry.get("id").intValue())); + if (entry.get("name").textValue().equals("minecraft:lodestone_compass")) { + lodestoneCompassId = entry.get("id").intValue(); + } } stream = FileUtils.getResource("mappings/items.json"); @@ -153,9 +164,6 @@ public class ItemRegistry { case "minecraft:bamboo": BAMBOO = ITEM_ENTRIES.get(itemIndex); break; - case "minecraft:oak_boat": - BOAT = ITEM_ENTRIES.get(itemIndex); - break; case "minecraft:egg": EGG = ITEM_ENTRIES.get(itemIndex); break; @@ -165,8 +173,8 @@ public class ItemRegistry { case "minecraft:shield": SHIELD = ITEM_ENTRIES.get(itemIndex); break; - case "minecraft:bucket": - BUCKET = ITEM_ENTRIES.get(itemIndex); + case "minecraft:milk_bucket": + MILK_BUCKET = ITEM_ENTRIES.get(itemIndex); break; case "minecraft:wheat": WHEAT = ITEM_ENTRIES.get(itemIndex); @@ -175,11 +183,22 @@ public class ItemRegistry { break; } + if (entry.getKey().contains("boat")) { + BOATS.add(entry.getValue().get("bedrock_id").intValue()); + } else if (entry.getKey().contains("bucket") && !entry.getKey().contains("milk")) { + BUCKETS.add(entry.getValue().get("bedrock_id").intValue()); + } + itemIndex++; } - // Add the loadstonecompass since it doesn't exist on java but we need it for item conversion - ITEM_ENTRIES.put(itemIndex, new ItemEntry("minecraft:lodestonecompass", itemIndex, 741, 0, false)); + if (lodestoneCompassId == 0) { + throw new RuntimeException("Lodestone compass not found in item palette!"); + } + + // Add the loadstone compass since it doesn't exist on java but we need it for item conversion + ITEM_ENTRIES.put(itemIndex, new ItemEntry("minecraft:lodestone_compass", itemIndex, + lodestoneCompassId, 0, false)); /* Load creative items */ stream = FileUtils.getResource("bedrock/creative_items.json"); @@ -256,7 +275,7 @@ public class ItemRegistry { * @param entry the ItemEntry to search for * @return the Bedrock identifier */ - public static String getBedrockIdentifer(ItemEntry entry) { + public static String getBedrockIdentifier(ItemEntry entry) { String blockName = ""; for (StartGamePacket.ItemEntry startGamePacketItemEntry : ItemRegistry.ITEMS) { if (startGamePacketItemEntry.getId() == (short) entry.getBedrockId()) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/CrossbowTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/CrossbowTranslator.java index 67f137ff..a05b9c8b 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/CrossbowTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/CrossbowTranslator.java @@ -53,7 +53,7 @@ public class CrossbowTranslator extends NbtItemStackTranslator { CompoundTag newProjectile = new CompoundTag("chargedItem"); newProjectile.put(new ByteTag("Count", (byte) itemData.getCount())); - newProjectile.put(new StringTag("Name", ItemRegistry.getBedrockIdentifer(entry))); + newProjectile.put(new StringTag("Name", ItemRegistry.getBedrockIdentifier(entry))); newProjectile.put(new ShortTag("Damage", itemData.getDamage())); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java index a9930f69..6ecb9a44 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java @@ -50,7 +50,7 @@ public class ShulkerBoxItemTranslator extends NbtItemStackTranslator { boxItemTag.put(new ByteTag("WasPickedUp", (byte) 0)); // ??? ItemEntry boxItemEntry = ItemRegistry.getItemEntry(((StringTag) itemData.get("id")).getValue()); - String blockName = ItemRegistry.getBedrockIdentifer(boxItemEntry); + String blockName = ItemRegistry.getBedrockIdentifier(boxItemEntry); boxItemTag.put(new StringTag("Name", blockName)); boxItemTag.put(new ShortTag("Damage", (short) boxItemEntry.getBedrockData())); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaExplosionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaExplosionTranslator.java index efed6ba4..4a0ea3ec 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaExplosionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaExplosionTranslator.java @@ -46,7 +46,7 @@ public class JavaExplosionTranslator extends PacketTranslator BLOCKS; - public static final int AIR = 0; + /** + * The Java block runtime ID of air + */ + public static final int JAVA_AIR_ID = 0; + /** + * The Bedrock block runtime ID of air + */ + public static final int BEDROCK_AIR_ID; public static final int BEDROCK_WATER_ID; private static final Int2IntMap JAVA_TO_BEDROCK_BLOCK_MAP = new Int2IntOpenHashMap(); @@ -86,25 +90,31 @@ public class BlockTranslator { public static final int JAVA_RUNTIME_SPAWNER_ID; - private static final int BLOCK_STATE_VERSION = 17825806; + private static final int BLOCK_STATE_VERSION = 17825808; static { /* Load block palette */ - InputStream stream = FileUtils.getResource("bedrock/runtime_block_states.dat"); + InputStream stream = FileUtils.getResource("bedrock/blockpalette.nbt"); NbtList blocksTag; - try (NBTInputStream nbtInputStream = NbtUtils.createNetworkReader(stream)) { - blocksTag = (NbtList) nbtInputStream.readTag(); + try (NBTInputStream nbtInputStream = new NBTInputStream(new DataInputStream(stream))) { + NbtMap blockPalette = (NbtMap) nbtInputStream.readTag(); + blocksTag = (NbtList) blockPalette.getList("blocks", NbtType.COMPOUND); } catch (Exception e) { throw new AssertionError("Unable to get blocks from runtime block states", e); } - Map blockStateMap = new HashMap<>(); + // New since 1.16.100 - find the block runtime ID by the order given to us in the block palette, + // as we no longer send a block palette + Object2IntMap blockStateOrderedMap = new Object2IntOpenHashMap<>(blocksTag.size()); - for (NbtMap tag : blocksTag) { - if (blockStateMap.putIfAbsent(tag.getCompound("block"), tag) != null) { + for (int i = 0; i < blocksTag.size(); i++) { + NbtMap tag = blocksTag.get(i); + NbtMap blockTag = tag.getCompound("block"); + if (blockStateOrderedMap.containsKey(blockTag)) { throw new AssertionError("Duplicate block states in Bedrock palette"); } + blockStateOrderedMap.put(blockTag, i); } stream = FileUtils.getResource("mappings/blocks.json"); @@ -114,16 +124,13 @@ public class BlockTranslator { } catch (Exception e) { throw new AssertionError("Unable to load Java block mappings", e); } - Object2IntMap addedStatesMap = new Object2IntOpenHashMap<>(); - addedStatesMap.defaultReturnValue(-1); - List paletteList = new ArrayList<>(); - Reflections ref = GeyserConnector.getInstance().useXmlReflections() ? FileUtils.getReflections("org.geysermc.connector.network.translators.world.block.entity") : new Reflections("org.geysermc.connector.network.translators.world.block.entity"); - ref.getTypesAnnotatedWith(BlockEntity.class); + Reflections ref = GeyserConnector.getInstance().useXmlReflections() ? FileUtils.getReflections("org.geysermc.connector.network.translators.world.block.entity") + : new Reflections("org.geysermc.connector.network.translators.world.block.entity"); int waterRuntimeId = -1; int javaRuntimeId = -1; - int bedrockRuntimeId = 0; + int airRuntimeId = -1; int cobwebRuntimeId = -1; int commandBlockRuntimeId = -1; int furnaceRuntimeId = -1; @@ -136,6 +143,10 @@ public class BlockTranslator { Map.Entry entry = blocksIterator.next(); String javaId = entry.getKey(); NbtMap blockTag = buildBedrockState(entry.getValue()); + int bedrockRuntimeId = blockStateOrderedMap.getOrDefault(blockTag, -1); + if (bedrockRuntimeId == -1) { + throw new RuntimeException("Unable to find " + javaId + " Bedrock runtime ID!"); + } // TODO fix this, (no block should have a null hardness) JsonNode hardnessNode = entry.getValue().get("block_hardness"); @@ -199,22 +210,12 @@ public class BlockTranslator { BEDROCK_TO_JAVA_BLOCK_MAP.putIfAbsent(bedrockRuntimeId, javaRuntimeId); } - NbtMap runtimeTag = blockStateMap.remove(blockTag); - if (runtimeTag != null) { - addedStatesMap.put(blockTag, bedrockRuntimeId); - paletteList.add(runtimeTag); - } else { - int duplicateRuntimeId = addedStatesMap.getOrDefault(blockTag, -1); - if (duplicateRuntimeId == -1) { - GeyserConnector.getInstance().getLogger().debug("Mapping " + javaId + " was not found for bedrock edition!"); - } else { - JAVA_TO_BEDROCK_BLOCK_MAP.put(javaRuntimeId, duplicateRuntimeId); - } - continue; - } JAVA_TO_BEDROCK_BLOCK_MAP.put(javaRuntimeId, bedrockRuntimeId); - if (javaId.contains("wool")) { + if (bedrockIdentifier.equals("minecraft:air")) { + airRuntimeId = bedrockRuntimeId; + + } else if (javaId.contains("wool")) { JAVA_RUNTIME_WOOL_IDS.add(javaRuntimeId); } else if (javaId.contains("cobweb")) { @@ -233,8 +234,6 @@ public class BlockTranslator { } else if (javaId.startsWith("minecraft:spawner")) { spawnerRuntimeId = javaRuntimeId; } - - bedrockRuntimeId++; } if (cobwebRuntimeId == -1) { @@ -267,19 +266,17 @@ public class BlockTranslator { } BEDROCK_WATER_ID = waterRuntimeId; - paletteList.addAll(blockStateMap.values()); // Add any missing mappings that could crash the client + if (airRuntimeId == -1) { + throw new AssertionError("Unable to find air in palette"); + } + BEDROCK_AIR_ID = airRuntimeId; // Loop around again to find all item frame runtime IDs - int frameRuntimeId = 0; - for (NbtMap tag : paletteList) { - NbtMap blockTag = tag.getCompound("block"); - if (blockTag.getString("name").equals("minecraft:frame")) { - ITEM_FRAMES.put(tag, frameRuntimeId); + for (Object2IntMap.Entry entry : blockStateOrderedMap.object2IntEntrySet()) { + if (entry.getKey().getString("name").equals("minecraft:frame")) { + ITEM_FRAMES.put(entry.getKey(), entry.getIntValue()); } - frameRuntimeId++; } - - BLOCKS = new NbtList<>(NbtType.COMPOUND, paletteList); } private BlockTranslator() { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/BlockStorage.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/BlockStorage.java index d8cd7520..f195394d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/BlockStorage.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/BlockStorage.java @@ -30,6 +30,7 @@ import io.netty.buffer.ByteBuf; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; import lombok.Getter; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.network.translators.world.chunk.bitarray.BitArray; import org.geysermc.connector.network.translators.world.chunk.bitarray.BitArrayVersion; @@ -50,7 +51,7 @@ public class BlockStorage { public BlockStorage(BitArrayVersion version) { this.bitArray = version.createArray(SIZE); this.palette = new IntArrayList(16); - this.palette.add(0); // Air is at the start of every palette. + this.palette.add(BlockTranslator.BEDROCK_AIR_ID); // Air is at the start of every palette and controls what the default block is in second-layer non-air block spaces. } public BlockStorage(BitArray bitArray, IntList palette) { diff --git a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java index 70ac76df..005a4960 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java @@ -68,7 +68,8 @@ import java.util.ArrayList; import java.util.BitSet; import java.util.List; -import static org.geysermc.connector.network.translators.world.block.BlockTranslator.AIR; +import static org.geysermc.connector.network.translators.world.block.BlockTranslator.JAVA_AIR_ID; +import static org.geysermc.connector.network.translators.world.block.BlockTranslator.BEDROCK_AIR_ID; import static org.geysermc.connector.network.translators.world.block.BlockTranslator.BEDROCK_WATER_ID; @UtilityClass @@ -246,7 +247,7 @@ public class ChunkUtils { // V1 palette IntList layer1Palette = new IntArrayList(2); - layer1Palette.add(0); // Air + layer1Palette.add(BEDROCK_AIR_ID); // Air - see BlockStorage's constructor for more information layer1Palette.add(BEDROCK_WATER_ID); layers = new BlockStorage[]{ layer0, new BlockStorage(BitArrayVersion.V1.createArray(BlockStorage.SIZE, layer1Data), layer1Palette) }; @@ -318,18 +319,32 @@ public class ChunkUtils { } } + /** + * Sends a block update to the Bedrock client. If chunk caching is enabled and the platform is not Spigot, this also + * adds that block to the cache. + * @param session the Bedrock session to send/register the block to + * @param blockState the Java block state of the block + * @param position the position of the block + */ public static void updateBlock(GeyserSession session, int blockState, Position position) { Vector3i pos = Vector3i.from(position.getX(), position.getY(), position.getZ()); updateBlock(session, blockState, pos); } + /** + * Sends a block update to the Bedrock client. If chunk caching is enabled and the platform is not Spigot, this also + * adds that block to the cache. + * @param session the Bedrock session to send/register the block to + * @param blockState the Java block state of the block + * @param position the position of the block + */ public static void updateBlock(GeyserSession session, int blockState, Vector3i position) { // Checks for item frames so they aren't tripped up and removed long frameEntityId = ItemFrameEntity.getItemFrameEntityId(session, position); if (frameEntityId != -1) { // TODO: Very occasionally the item frame doesn't sync up when destroyed Entity entity = session.getEntityCache().getEntityByJavaId(frameEntityId); - if (blockState == AIR && entity != null) { // Item frame is still present and no block overrides that; refresh it + if (blockState == JAVA_AIR_ID && entity != null) { // Item frame is still present and no block overrides that; refresh it ((ItemFrameEntity) entity).updateBlock(session); return; } @@ -358,7 +373,7 @@ public class ChunkUtils { if (BlockTranslator.isWaterlogged(blockState)) { waterPacket.setRuntimeId(BEDROCK_WATER_ID); } else { - waterPacket.setRuntimeId(0); + waterPacket.setRuntimeId(BEDROCK_AIR_ID); } session.sendUpstreamPacket(waterPacket); diff --git a/connector/src/main/resources/bedrock/blockpalette.nbt b/connector/src/main/resources/bedrock/blockpalette.nbt new file mode 100644 index 0000000000000000000000000000000000000000..b92a06260912dfd8e741a6c8f559af277d6c9775 GIT binary patch literal 1160789 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 literal 0 HcmV?d00001 diff --git a/connector/src/main/resources/bedrock/creative_items.json b/connector/src/main/resources/bedrock/creative_items.json index eea92a95..90839b0d 100644 --- a/connector/src/main/resources/bedrock/creative_items.json +++ b/connector/src/main/resources/bedrock/creative_items.json @@ -258,31 +258,31 @@ "id" : -275 }, { - "id" : 324 + "id" : 359 }, { - "id" : 427 + "id" : 543 }, { - "id" : 428 + "id" : 544 }, { - "id" : 429 + "id" : 545 }, { - "id" : 430 + "id" : 546 }, { - "id" : 431 + "id" : 547 }, { - "id" : 330 + "id" : 370 }, { - "id" : 755 + "id" : 604 }, { - "id" : 756 + "id" : 605 }, { "id" : 96 @@ -314,9 +314,6 @@ { "id" : 101 }, - { - "id" : 758 - }, { "id" : 20 }, @@ -741,66 +738,15 @@ { "id" : 170 }, - { - "id" : -239 - }, { "id" : 216 }, - { - "id" : 214 - }, - { - "id" : -227 - }, { "id" : 112 }, { "id" : 215 }, - { - "id" : -225 - }, - { - "id" : -226 - }, - { - "id" : -240 - }, - { - "id" : -241 - }, - { - "id" : -299 - }, - { - "id" : -298 - }, - { - "id" : -300 - }, - { - "id" : -301 - }, - { - "id" : -230 - }, - { - "id" : -232 - }, - { - "id" : -233 - }, - { - "id" : -234 - }, - { - "id" : -235 - }, - { - "id" : -236 - }, { "id" : -270 }, @@ -1183,6 +1129,30 @@ "id" : 201, "damage" : 2 }, + { + "id" : 214 + }, + { + "id" : -227 + }, + { + "id" : -230 + }, + { + "id" : -232 + }, + { + "id" : -233 + }, + { + "id" : -234 + }, + { + "id" : -235 + }, + { + "id" : -236 + }, { "id" : 3 }, @@ -1190,6 +1160,9 @@ "id" : 3, "damage" : 1 }, + { + "id" : 60 + }, { "id" : 2 }, @@ -1318,6 +1291,18 @@ { "id" : -9 }, + { + "id" : -225 + }, + { + "id" : -240 + }, + { + "id" : -226 + }, + { + "id" : -241 + }, { "id" : -212 }, @@ -1365,6 +1350,18 @@ "id" : -212, "damage" : 13 }, + { + "id" : -299 + }, + { + "id" : -300 + }, + { + "id" : -298 + }, + { + "id" : -301 + }, { "id" : 18 }, @@ -1411,59 +1408,58 @@ "damage" : 5 }, { - "id" : -218, - "damage" : 3 + "id" : -218 + }, + { + "id" : 291 + }, + { + "id" : 292 + }, + { + "id" : 293 }, { "id" : 295 }, { - "id" : 361 + "id" : 334 }, { - "id" : 362 + "id" : 285 }, { - "id" : 458 + "id" : 280 }, { - "id" : 296 + "id" : 282 }, { - "id" : 457 + "id" : 279 }, { - "id" : 392 + "id" : 283 }, { - "id" : 394 + "id" : 257 }, { - "id" : 391 + "id" : 258 }, { - "id" : 396 - }, - { - "id" : 260 - }, - { - "id" : 322 - }, - { - "id" : 466 + "id" : 259 }, { "id" : 103 }, { - "id" : 360 + "id" : 272 }, { - "id" : 382 + "id" : 432 }, { - "id" : 477 + "id" : 287 }, { "id" : 86 @@ -1475,7 +1471,7 @@ "id" : 91 }, { - "id" : 736 + "id" : 580 }, { "id" : 31, @@ -1494,7 +1490,7 @@ "damage" : 2 }, { - "id" : 760 + "id" : 609 }, { "id" : -131, @@ -1574,7 +1570,7 @@ "damage" : 4 }, { - "id" : 335 + "id" : 380 }, { "id" : -130 @@ -1650,83 +1646,64 @@ "id" : -216 }, { - "id" : 351, - "damage" : 19 + "id" : 408 }, { - "id" : 351, - "damage" : 7 + "id" : 400 }, { - "id" : 351, - "damage" : 8 + "id" : 401 }, { - "id" : 351, - "damage" : 16 + "id" : 393 }, { - "id" : 351, - "damage" : 17 + "id" : 396 }, { - "id" : 351, - "damage" : 1 + "id" : 394 }, { - "id" : 351, - "damage" : 14 + "id" : 407 }, { - "id" : 351, - "damage" : 11 + "id" : 404 }, { - "id" : 351, - "damage" : 10 + "id" : 403 }, { - "id" : 351, - "damage" : 2 + "id" : 395 }, { - "id" : 351, - "damage" : 6 + "id" : 399 }, { - "id" : 351, - "damage" : 12 + "id" : 405 }, { - "id" : 351, - "damage" : 18 + "id" : 397 }, { - "id" : 351, - "damage" : 5 + "id" : 398 }, { - "id" : 351, - "damage" : 13 + "id" : 406 }, { - "id" : 351, - "damage" : 9 + "id" : 402 }, { - "id" : 351 + "id" : 411 }, { - "id" : 351, - "damage" : 3 + "id" : 410 }, { - "id" : 351, - "damage" : 4 + "id" : 412 }, { - "id" : 351, - "damage" : 15 + "id" : 409 }, { "id" : 106 @@ -1762,31 +1739,31 @@ "id" : 78 }, { - "id" : 365 + "id" : 275 }, { - "id" : 319 + "id" : 262 }, { - "id" : 363 + "id" : 273 }, { - "id" : 423 + "id" : 540 }, { - "id" : 411 + "id" : 288 }, { - "id" : 349 + "id" : 264 }, { - "id" : 460 + "id" : 265 }, { - "id" : 461 + "id" : 266 }, { - "id" : 462 + "id" : 267 }, { "id" : 39 @@ -1816,25 +1793,25 @@ "id" : 99 }, { - "id" : 344 + "id" : 388 }, { - "id" : 338 + "id" : 383 }, { - "id" : 353 + "id" : 414 }, { - "id" : 367 + "id" : 277 }, { - "id" : 352 + "id" : 413 }, { "id" : 30 }, { - "id" : 375 + "id" : 278 }, { "id" : 52 @@ -1869,252 +1846,193 @@ "id" : -159 }, { - "id" : 383, - "damage" : 10 + "id" : 433 }, { - "id" : 383, - "damage" : 122 + "id" : 492 }, { - "id" : 383, - "damage" : 11 + "id" : 434 }, { - "id" : 383, - "damage" : 12 + "id" : 435 }, { - "id" : 383, - "damage" : 13 + "id" : 436 }, { - "id" : 383, - "damage" : 14 + "id" : 437 }, { - "id" : 383, - "damage" : 28 + "id" : 470 }, { - "id" : 383, - "damage" : 22 + "id" : 449 }, { - "id" : 383, - "damage" : 75 + "id" : 486 }, { - "id" : 383, - "damage" : 16 + "id" : 438 }, { - "id" : 383, - "damage" : 19 + "id" : 451 }, { - "id" : 383, - "damage" : 30 + "id" : 476 }, { - "id" : 383, - "damage" : 18 + "id" : 457 }, { - "id" : 383, - "damage" : 29 + "id" : 471 }, { - "id" : 383, - "damage" : 23 + "id" : 456 }, { - "id" : 383, - "damage" : 24 + "id" : 463 }, { - "id" : 383, - "damage" : 25 + "id" : 464 }, { - "id" : 383, - "damage" : 26 + "id" : 465 }, { - "id" : 383, - "damage" : 27 + "id" : 466 }, { - "id" : 383, - "damage" : 111 + "id" : 477 }, { - "id" : 383, - "damage" : 112 + "id" : 478 }, { - "id" : 383, - "damage" : 108 + "id" : 479 }, { - "id" : 383, - "damage" : 109 + "id" : 480 }, { - "id" : 383, - "damage" : 31 + "id" : 482 }, { - "id" : 383, - "damage" : 74 + "id" : 483 }, { - "id" : 383, - "damage" : 113 + "id" : 487 }, { - "id" : 383, - "damage" : 121 + "id" : 488 }, { - "id" : 383, - "damage" : 33 + "id" : 439 }, { - "id" : 383, - "damage" : 38 + "id" : 440 }, { - "id" : 383, - "damage" : 39 + "id" : 441 }, { - "id" : 383, - "damage" : 34 + "id" : 442 }, { - "id" : 383, - "damage" : 48 + "id" : 462 }, { - "id" : 383, - "damage" : 46 + "id" : 460 }, { - "id" : 383, - "damage" : 37 + "id" : 443 }, { - "id" : 383, - "damage" : 35 + "id" : 444 }, { - "id" : 383, - "damage" : 32 + "id" : 445 }, { - "id" : 383, - "damage" : 36 + "id" : 446 }, { - "id" : 383, - "damage" : 47 + "id" : 461 }, { - "id" : 383, - "damage" : 110 + "id" : 481 }, { - "id" : 383, - "damage" : 17 + "id" : 448 }, { - "id" : 383, - "damage" : 40 + "id" : 455 }, { - "id" : 383, - "damage" : 45 + "id" : 450 }, { - "id" : 383, - "damage" : 49 + "id" : 459 }, { - "id" : 383, - "damage" : 50 + "id" : 469 }, { - "id" : 383, - "damage" : 55 + "id" : 458 }, { - "id" : 383, - "damage" : 42 + "id" : 453 }, { - "id" : 383, - "damage" : 125 + "id" : 493 }, { - "id" : 383, - "damage" : 124 + "id" : 494 }, { - "id" : 383, - "damage" : 123 + "id" : 495 }, { - "id" : 383, - "damage" : 126 + "id" : 496 }, { - "id" : 383, - "damage" : 41 + "id" : 497 }, { - "id" : 383, - "damage" : 43 + "id" : 452 }, { - "id" : 383, - "damage" : 54 + "id" : 454 }, { - "id" : 383, - "damage" : 57 + "id" : 467 }, { - "id" : 383, - "damage" : 104 + "id" : 472 }, { - "id" : 383, - "damage" : 105 + "id" : 473 }, { - "id" : 383, - "damage" : 115 + "id" : 474 }, { - "id" : 383, - "damage" : 118 + "id" : 447 }, { - "id" : 383, - "damage" : 116 + "id" : 490 }, { - "id" : 383, - "damage" : 58 + "id" : 475 }, { - "id" : 383, - "damage" : 114 + "id" : 484 }, { - "id" : 383, - "damage" : 59 + "id" : 489 + }, + { + "id" : 491 }, { "id" : 49 @@ -2135,7 +2053,7 @@ "id" : 213 }, { - "id" : 372 + "id" : 294 }, { "id" : 121 @@ -2147,10 +2065,10 @@ "id" : 240 }, { - "id" : 432 + "id" : 548 }, { - "id" : 433 + "id" : 549 }, { "id" : 19 @@ -2199,1023 +2117,1023 @@ "damage" : 12 }, { - "id" : 298 + "id" : 335 }, { - "id" : 302 + "id" : 339 }, { - "id" : 306 + "id" : 343 }, { - "id" : 314 - }, - { - "id" : 310 - }, - { - "id" : 748 - }, - { - "id" : 299 - }, - { - "id" : 303 - }, - { - "id" : 307 - }, - { - "id" : 315 - }, - { - "id" : 311 - }, - { - "id" : 749 - }, - { - "id" : 300 - }, - { - "id" : 304 - }, - { - "id" : 308 - }, - { - "id" : 316 - }, - { - "id" : 312 - }, - { - "id" : 750 - }, - { - "id" : 301 - }, - { - "id" : 305 - }, - { - "id" : 309 - }, - { - "id" : 317 - }, - { - "id" : 313 - }, - { - "id" : 751 - }, - { - "id" : 268 - }, - { - "id" : 272 - }, - { - "id" : 267 - }, - { - "id" : 283 - }, - { - "id" : 276 - }, - { - "id" : 743 - }, - { - "id" : 271 - }, - { - "id" : 275 - }, - { - "id" : 258 - }, - { - "id" : 286 - }, - { - "id" : 279 - }, - { - "id" : 746 - }, - { - "id" : 270 - }, - { - "id" : 274 - }, - { - "id" : 257 - }, - { - "id" : 285 - }, - { - "id" : 278 - }, - { - "id" : 745 - }, - { - "id" : 269 - }, - { - "id" : 273 - }, - { - "id" : 256 - }, - { - "id" : 284 - }, - { - "id" : 277 - }, - { - "id" : 744 - }, - { - "id" : 290 - }, - { - "id" : 291 - }, - { - "id" : 292 - }, - { - "id" : 294 - }, - { - "id" : 293 - }, - { - "id" : 747 - }, - { - "id" : 261 - }, - { - "id" : 471 - }, - { - "id" : 262 - }, - { - "id" : 262, - "damage" : 6 - }, - { - "id" : 262, - "damage" : 7 - }, - { - "id" : 262, - "damage" : 8 - }, - { - "id" : 262, - "damage" : 9 - }, - { - "id" : 262, - "damage" : 10 - }, - { - "id" : 262, - "damage" : 11 - }, - { - "id" : 262, - "damage" : 12 - }, - { - "id" : 262, - "damage" : 13 - }, - { - "id" : 262, - "damage" : 14 - }, - { - "id" : 262, - "damage" : 15 - }, - { - "id" : 262, - "damage" : 16 - }, - { - "id" : 262, - "damage" : 17 - }, - { - "id" : 262, - "damage" : 18 - }, - { - "id" : 262, - "damage" : 19 - }, - { - "id" : 262, - "damage" : 20 - }, - { - "id" : 262, - "damage" : 21 - }, - { - "id" : 262, - "damage" : 22 - }, - { - "id" : 262, - "damage" : 23 - }, - { - "id" : 262, - "damage" : 24 - }, - { - "id" : 262, - "damage" : 25 - }, - { - "id" : 262, - "damage" : 26 - }, - { - "id" : 262, - "damage" : 27 - }, - { - "id" : 262, - "damage" : 28 - }, - { - "id" : 262, - "damage" : 29 - }, - { - "id" : 262, - "damage" : 30 - }, - { - "id" : 262, - "damage" : 31 - }, - { - "id" : 262, - "damage" : 32 - }, - { - "id" : 262, - "damage" : 33 - }, - { - "id" : 262, - "damage" : 34 - }, - { - "id" : 262, - "damage" : 35 - }, - { - "id" : 262, - "damage" : 36 - }, - { - "id" : 262, - "damage" : 37 - }, - { - "id" : 262, - "damage" : 38 - }, - { - "id" : 262, - "damage" : 39 - }, - { - "id" : 262, - "damage" : 40 - }, - { - "id" : 262, - "damage" : 41 - }, - { - "id" : 262, - "damage" : 42 - }, - { - "id" : 262, - "damage" : 43 - }, - { - "id" : 513 - }, - { - "id" : 366 - }, - { - "id" : 320 - }, - { - "id" : 364 - }, - { - "id" : 424 - }, - { - "id" : 412 - }, - { - "id" : 350 - }, - { - "id" : 463 - }, - { - "id" : 297 - }, - { - "id" : 282 - }, - { - "id" : 459 - }, - { - "id" : 413 - }, - { - "id" : 393 - }, - { - "id" : 357 - }, - { - "id" : 400 - }, - { - "id" : 354 - }, - { - "id" : 464 - }, - { - "id" : 346 - }, - { - "id" : 398 - }, - { - "id" : 757 - }, - { - "id" : 332 - }, - { - "id" : 359 - }, - { - "id" : 259 - }, - { - "id" : 420 + "id" : 351 }, { "id" : 347 }, + { + "id" : 597 + }, + { + "id" : 336 + }, + { + "id" : 340 + }, + { + "id" : 344 + }, + { + "id" : 352 + }, + { + "id" : 348 + }, + { + "id" : 598 + }, + { + "id" : 337 + }, + { + "id" : 341 + }, { "id" : 345 }, { - "id" : 395 + "id" : 353 }, { - "id" : 395, - "damage" : 2 + "id" : 349 + }, + { + "id" : 599 + }, + { + "id" : 338 + }, + { + "id" : 342 + }, + { + "id" : 346 + }, + { + "id" : 354 + }, + { + "id" : 350 + }, + { + "id" : 600 + }, + { + "id" : 308 + }, + { + "id" : 312 + }, + { + "id" : 307 + }, + { + "id" : 322 + }, + { + "id" : 316 + }, + { + "id" : 592 + }, + { + "id" : 311 + }, + { + "id" : 315 + }, + { + "id" : 298 + }, + { + "id" : 325 + }, + { + "id" : 319 + }, + { + "id" : 595 + }, + { + "id" : 310 + }, + { + "id" : 314 + }, + { + "id" : 297 + }, + { + "id" : 324 + }, + { + "id" : 318 + }, + { + "id" : 594 + }, + { + "id" : 309 + }, + { + "id" : 313 + }, + { + "id" : 296 + }, + { + "id" : 323 + }, + { + "id" : 317 + }, + { + "id" : 593 }, { "id" : 329 }, { - "id" : 416 + "id" : 330 }, { - "id" : 417 + "id" : 331 }, { - "id" : 418 + "id" : 333 }, { - "id" : 419 + "id" : 332 }, { - "id" : 455 + "id" : 596 }, { - "id" : 469 + "id" : 300 }, { - "id" : 444 + "id" : 565 }, { - "id" : 450 + "id" : 301 }, { - "id" : 374 - }, - { - "id" : 384 - }, - { - "id" : 373 - }, - { - "id" : 373, - "damage" : 1 - }, - { - "id" : 373, - "damage" : 2 - }, - { - "id" : 373, - "damage" : 3 - }, - { - "id" : 373, - "damage" : 4 - }, - { - "id" : 373, - "damage" : 5 - }, - { - "id" : 373, + "id" : 301, "damage" : 6 }, { - "id" : 373, + "id" : 301, "damage" : 7 }, { - "id" : 373, + "id" : 301, "damage" : 8 }, { - "id" : 373, + "id" : 301, "damage" : 9 }, { - "id" : 373, + "id" : 301, "damage" : 10 }, { - "id" : 373, + "id" : 301, "damage" : 11 }, { - "id" : 373, + "id" : 301, "damage" : 12 }, { - "id" : 373, + "id" : 301, "damage" : 13 }, { - "id" : 373, + "id" : 301, "damage" : 14 }, { - "id" : 373, + "id" : 301, "damage" : 15 }, { - "id" : 373, + "id" : 301, "damage" : 16 }, { - "id" : 373, + "id" : 301, "damage" : 17 }, { - "id" : 373, + "id" : 301, "damage" : 18 }, { - "id" : 373, + "id" : 301, "damage" : 19 }, { - "id" : 373, + "id" : 301, "damage" : 20 }, { - "id" : 373, + "id" : 301, "damage" : 21 }, { - "id" : 373, + "id" : 301, "damage" : 22 }, { - "id" : 373, + "id" : 301, "damage" : 23 }, { - "id" : 373, + "id" : 301, "damage" : 24 }, { - "id" : 373, + "id" : 301, "damage" : 25 }, { - "id" : 373, + "id" : 301, "damage" : 26 }, { - "id" : 373, + "id" : 301, "damage" : 27 }, { - "id" : 373, + "id" : 301, "damage" : 28 }, { - "id" : 373, + "id" : 301, "damage" : 29 }, { - "id" : 373, + "id" : 301, "damage" : 30 }, { - "id" : 373, + "id" : 301, "damage" : 31 }, { - "id" : 373, + "id" : 301, "damage" : 32 }, { - "id" : 373, + "id" : 301, "damage" : 33 }, { - "id" : 373, + "id" : 301, "damage" : 34 }, { - "id" : 373, + "id" : 301, "damage" : 35 }, { - "id" : 373, + "id" : 301, "damage" : 36 }, { - "id" : 373, + "id" : 301, "damage" : 37 }, { - "id" : 373, + "id" : 301, "damage" : 38 }, { - "id" : 373, + "id" : 301, "damage" : 39 }, { - "id" : 373, + "id" : 301, "damage" : 40 }, { - "id" : 373, + "id" : 301, "damage" : 41 }, { - "id" : 373, + "id" : 301, "damage" : 42 }, { - "id" : 438 - }, - { - "id" : 438, - "damage" : 1 - }, - { - "id" : 438, - "damage" : 2 - }, - { - "id" : 438, - "damage" : 3 - }, - { - "id" : 438, - "damage" : 4 - }, - { - "id" : 438, - "damage" : 5 - }, - { - "id" : 438, - "damage" : 6 - }, - { - "id" : 438, - "damage" : 7 - }, - { - "id" : 438, - "damage" : 8 - }, - { - "id" : 438, - "damage" : 9 - }, - { - "id" : 438, - "damage" : 10 - }, - { - "id" : 438, - "damage" : 11 - }, - { - "id" : 438, - "damage" : 12 - }, - { - "id" : 438, - "damage" : 13 - }, - { - "id" : 438, - "damage" : 14 - }, - { - "id" : 438, - "damage" : 15 - }, - { - "id" : 438, - "damage" : 16 - }, - { - "id" : 438, - "damage" : 17 - }, - { - "id" : 438, - "damage" : 18 - }, - { - "id" : 438, - "damage" : 19 - }, - { - "id" : 438, - "damage" : 20 - }, - { - "id" : 438, - "damage" : 21 - }, - { - "id" : 438, - "damage" : 22 - }, - { - "id" : 438, - "damage" : 23 - }, - { - "id" : 438, - "damage" : 24 - }, - { - "id" : 438, - "damage" : 25 - }, - { - "id" : 438, - "damage" : 26 - }, - { - "id" : 438, - "damage" : 27 - }, - { - "id" : 438, - "damage" : 28 - }, - { - "id" : 438, - "damage" : 29 - }, - { - "id" : 438, - "damage" : 30 - }, - { - "id" : 438, - "damage" : 31 - }, - { - "id" : 438, - "damage" : 32 - }, - { - "id" : 438, - "damage" : 33 - }, - { - "id" : 438, - "damage" : 34 - }, - { - "id" : 438, - "damage" : 35 - }, - { - "id" : 438, - "damage" : 36 - }, - { - "id" : 438, - "damage" : 37 - }, - { - "id" : 438, - "damage" : 38 - }, - { - "id" : 438, - "damage" : 39 - }, - { - "id" : 438, - "damage" : 40 - }, - { - "id" : 438, - "damage" : 41 - }, - { - "id" : 438, - "damage" : 42 - }, - { - "id" : 441 - }, - { - "id" : 441, - "damage" : 1 - }, - { - "id" : 441, - "damage" : 2 - }, - { - "id" : 441, - "damage" : 3 - }, - { - "id" : 441, - "damage" : 4 - }, - { - "id" : 441, - "damage" : 5 - }, - { - "id" : 441, - "damage" : 6 - }, - { - "id" : 441, - "damage" : 7 - }, - { - "id" : 441, - "damage" : 8 - }, - { - "id" : 441, - "damage" : 9 - }, - { - "id" : 441, - "damage" : 10 - }, - { - "id" : 441, - "damage" : 11 - }, - { - "id" : 441, - "damage" : 12 - }, - { - "id" : 441, - "damage" : 13 - }, - { - "id" : 441, - "damage" : 14 - }, - { - "id" : 441, - "damage" : 15 - }, - { - "id" : 441, - "damage" : 16 - }, - { - "id" : 441, - "damage" : 17 - }, - { - "id" : 441, - "damage" : 18 - }, - { - "id" : 441, - "damage" : 19 - }, - { - "id" : 441, - "damage" : 20 - }, - { - "id" : 441, - "damage" : 21 - }, - { - "id" : 441, - "damage" : 22 - }, - { - "id" : 441, - "damage" : 23 - }, - { - "id" : 441, - "damage" : 24 - }, - { - "id" : 441, - "damage" : 25 - }, - { - "id" : 441, - "damage" : 26 - }, - { - "id" : 441, - "damage" : 27 - }, - { - "id" : 441, - "damage" : 28 - }, - { - "id" : 441, - "damage" : 29 - }, - { - "id" : 441, - "damage" : 30 - }, - { - "id" : 441, - "damage" : 31 - }, - { - "id" : 441, - "damage" : 32 - }, - { - "id" : 441, - "damage" : 33 - }, - { - "id" : 441, - "damage" : 34 - }, - { - "id" : 441, - "damage" : 35 - }, - { - "id" : 441, - "damage" : 36 - }, - { - "id" : 441, - "damage" : 37 - }, - { - "id" : 441, - "damage" : 38 - }, - { - "id" : 441, - "damage" : 39 - }, - { - "id" : 441, - "damage" : 40 - }, - { - "id" : 441, - "damage" : 41 - }, - { - "id" : 441, - "damage" : 42 - }, - { - "id" : 280 + "id" : 301, + "damage" : 43 }, { "id" : 355 }, { - "id" : 355, - "damage" : 8 + "id" : 276 }, { - "id" : 355, - "damage" : 7 + "id" : 263 }, { - "id" : 355, - "damage" : 15 + "id" : 274 }, { - "id" : 355, - "damage" : 12 + "id" : 541 }, { - "id" : 355, - "damage" : 14 + "id" : 289 }, { - "id" : 355, - "damage" : 1 + "id" : 268 }, { - "id" : 355, - "damage" : 4 + "id" : 269 }, { - "id" : 355, - "damage" : 5 + "id" : 261 }, { - "id" : 355, - "damage" : 13 + "id" : 260 }, { - "id" : 355, - "damage" : 9 + "id" : 286 }, { - "id" : 355, - "damage" : 3 + "id" : 290 }, { - "id" : 355, - "damage" : 11 + "id" : 281 }, { - "id" : 355, - "damage" : 10 + "id" : 271 }, { - "id" : 355, + "id" : 284 + }, + { + "id" : 415 + }, + { + "id" : 270 + }, + { + "id" : 390 + }, + { + "id" : 507 + }, + { + "id" : 606 + }, + { + "id" : 372 + }, + { + "id" : 419 + }, + { + "id" : 299 + }, + { + "id" : 537 + }, + { + "id" : 391 + }, + { + "id" : 389 + }, + { + "id" : 505 + }, + { + "id" : 505, "damage" : 2 }, { - "id" : 355, + "id" : 369 + }, + { + "id" : 520 + }, + { + "id" : 521 + }, + { + "id" : 522 + }, + { + "id" : 523 + }, + { + "id" : 536 + }, + { + "id" : 563 + }, + { + "id" : 554 + }, + { + "id" : 558 + }, + { + "id" : 425 + }, + { + "id" : 498 + }, + { + "id" : 424 + }, + { + "id" : 424, + "damage" : 1 + }, + { + "id" : 424, + "damage" : 2 + }, + { + "id" : 424, + "damage" : 3 + }, + { + "id" : 424, + "damage" : 4 + }, + { + "id" : 424, + "damage" : 5 + }, + { + "id" : 424, + "damage" : 6 + }, + { + "id" : 424, + "damage" : 7 + }, + { + "id" : 424, + "damage" : 8 + }, + { + "id" : 424, + "damage" : 9 + }, + { + "id" : 424, + "damage" : 10 + }, + { + "id" : 424, + "damage" : 11 + }, + { + "id" : 424, + "damage" : 12 + }, + { + "id" : 424, + "damage" : 13 + }, + { + "id" : 424, + "damage" : 14 + }, + { + "id" : 424, + "damage" : 15 + }, + { + "id" : 424, + "damage" : 16 + }, + { + "id" : 424, + "damage" : 17 + }, + { + "id" : 424, + "damage" : 18 + }, + { + "id" : 424, + "damage" : 19 + }, + { + "id" : 424, + "damage" : 20 + }, + { + "id" : 424, + "damage" : 21 + }, + { + "id" : 424, + "damage" : 22 + }, + { + "id" : 424, + "damage" : 23 + }, + { + "id" : 424, + "damage" : 24 + }, + { + "id" : 424, + "damage" : 25 + }, + { + "id" : 424, + "damage" : 26 + }, + { + "id" : 424, + "damage" : 27 + }, + { + "id" : 424, + "damage" : 28 + }, + { + "id" : 424, + "damage" : 29 + }, + { + "id" : 424, + "damage" : 30 + }, + { + "id" : 424, + "damage" : 31 + }, + { + "id" : 424, + "damage" : 32 + }, + { + "id" : 424, + "damage" : 33 + }, + { + "id" : 424, + "damage" : 34 + }, + { + "id" : 424, + "damage" : 35 + }, + { + "id" : 424, + "damage" : 36 + }, + { + "id" : 424, + "damage" : 37 + }, + { + "id" : 424, + "damage" : 38 + }, + { + "id" : 424, + "damage" : 39 + }, + { + "id" : 424, + "damage" : 40 + }, + { + "id" : 424, + "damage" : 41 + }, + { + "id" : 424, + "damage" : 42 + }, + { + "id" : 551 + }, + { + "id" : 551, + "damage" : 1 + }, + { + "id" : 551, + "damage" : 2 + }, + { + "id" : 551, + "damage" : 3 + }, + { + "id" : 551, + "damage" : 4 + }, + { + "id" : 551, + "damage" : 5 + }, + { + "id" : 551, + "damage" : 6 + }, + { + "id" : 551, + "damage" : 7 + }, + { + "id" : 551, + "damage" : 8 + }, + { + "id" : 551, + "damage" : 9 + }, + { + "id" : 551, + "damage" : 10 + }, + { + "id" : 551, + "damage" : 11 + }, + { + "id" : 551, + "damage" : 12 + }, + { + "id" : 551, + "damage" : 13 + }, + { + "id" : 551, + "damage" : 14 + }, + { + "id" : 551, + "damage" : 15 + }, + { + "id" : 551, + "damage" : 16 + }, + { + "id" : 551, + "damage" : 17 + }, + { + "id" : 551, + "damage" : 18 + }, + { + "id" : 551, + "damage" : 19 + }, + { + "id" : 551, + "damage" : 20 + }, + { + "id" : 551, + "damage" : 21 + }, + { + "id" : 551, + "damage" : 22 + }, + { + "id" : 551, + "damage" : 23 + }, + { + "id" : 551, + "damage" : 24 + }, + { + "id" : 551, + "damage" : 25 + }, + { + "id" : 551, + "damage" : 26 + }, + { + "id" : 551, + "damage" : 27 + }, + { + "id" : 551, + "damage" : 28 + }, + { + "id" : 551, + "damage" : 29 + }, + { + "id" : 551, + "damage" : 30 + }, + { + "id" : 551, + "damage" : 31 + }, + { + "id" : 551, + "damage" : 32 + }, + { + "id" : 551, + "damage" : 33 + }, + { + "id" : 551, + "damage" : 34 + }, + { + "id" : 551, + "damage" : 35 + }, + { + "id" : 551, + "damage" : 36 + }, + { + "id" : 551, + "damage" : 37 + }, + { + "id" : 551, + "damage" : 38 + }, + { + "id" : 551, + "damage" : 39 + }, + { + "id" : 551, + "damage" : 40 + }, + { + "id" : 551, + "damage" : 41 + }, + { + "id" : 551, + "damage" : 42 + }, + { + "id" : 552 + }, + { + "id" : 552, + "damage" : 1 + }, + { + "id" : 552, + "damage" : 2 + }, + { + "id" : 552, + "damage" : 3 + }, + { + "id" : 552, + "damage" : 4 + }, + { + "id" : 552, + "damage" : 5 + }, + { + "id" : 552, + "damage" : 6 + }, + { + "id" : 552, + "damage" : 7 + }, + { + "id" : 552, + "damage" : 8 + }, + { + "id" : 552, + "damage" : 9 + }, + { + "id" : 552, + "damage" : 10 + }, + { + "id" : 552, + "damage" : 11 + }, + { + "id" : 552, + "damage" : 12 + }, + { + "id" : 552, + "damage" : 13 + }, + { + "id" : 552, + "damage" : 14 + }, + { + "id" : 552, + "damage" : 15 + }, + { + "id" : 552, + "damage" : 16 + }, + { + "id" : 552, + "damage" : 17 + }, + { + "id" : 552, + "damage" : 18 + }, + { + "id" : 552, + "damage" : 19 + }, + { + "id" : 552, + "damage" : 20 + }, + { + "id" : 552, + "damage" : 21 + }, + { + "id" : 552, + "damage" : 22 + }, + { + "id" : 552, + "damage" : 23 + }, + { + "id" : 552, + "damage" : 24 + }, + { + "id" : 552, + "damage" : 25 + }, + { + "id" : 552, + "damage" : 26 + }, + { + "id" : 552, + "damage" : 27 + }, + { + "id" : 552, + "damage" : 28 + }, + { + "id" : 552, + "damage" : 29 + }, + { + "id" : 552, + "damage" : 30 + }, + { + "id" : 552, + "damage" : 31 + }, + { + "id" : 552, + "damage" : 32 + }, + { + "id" : 552, + "damage" : 33 + }, + { + "id" : 552, + "damage" : 34 + }, + { + "id" : 552, + "damage" : 35 + }, + { + "id" : 552, + "damage" : 36 + }, + { + "id" : 552, + "damage" : 37 + }, + { + "id" : 552, + "damage" : 38 + }, + { + "id" : 552, + "damage" : 39 + }, + { + "id" : 552, + "damage" : 40 + }, + { + "id" : 552, + "damage" : 41 + }, + { + "id" : 552, + "damage" : 42 + }, + { + "id" : 320 + }, + { + "id" : 416 + }, + { + "id" : 416, + "damage" : 8 + }, + { + "id" : 416, + "damage" : 7 + }, + { + "id" : 416, + "damage" : 15 + }, + { + "id" : 416, + "damage" : 12 + }, + { + "id" : 416, + "damage" : 14 + }, + { + "id" : 416, + "damage" : 1 + }, + { + "id" : 416, + "damage" : 4 + }, + { + "id" : 416, + "damage" : 5 + }, + { + "id" : 416, + "damage" : 13 + }, + { + "id" : 416, + "damage" : 9 + }, + { + "id" : 416, + "damage" : 3 + }, + { + "id" : 416, + "damage" : 11 + }, + { + "id" : 416, + "damage" : 10 + }, + { + "id" : 416, + "damage" : 2 + }, + { + "id" : 416, "damage" : 6 }, { @@ -3246,14 +3164,13 @@ "id" : -202 }, { - "id" : -219, - "damage" : 3 + "id" : -219 }, { - "id" : 720 + "id" : 578 }, { - "id" : 801 + "id" : 610 }, { "id" : 61 @@ -3268,7 +3185,7 @@ "id" : -272 }, { - "id" : 379 + "id" : 429 }, { "id" : 145 @@ -3294,7 +3211,7 @@ "id" : -194 }, { - "id" : 380 + "id" : 430 }, { "id" : -213 @@ -3378,7 +3295,7 @@ "damage" : 6 }, { - "id" : 425 + "id" : 542 }, { "id" : 25 @@ -3387,46 +3304,46 @@ "id" : 84 }, { - "id" : 500 + "id" : 524 }, { - "id" : 501 + "id" : 525 }, { - "id" : 502 + "id" : 526 }, { - "id" : 503 + "id" : 527 }, { - "id" : 504 + "id" : 528 }, { - "id" : 505 + "id" : 529 }, { - "id" : 506 + "id" : 530 }, { - "id" : 507 + "id" : 531 }, { - "id" : 508 + "id" : 532 }, { - "id" : 509 + "id" : 533 }, { - "id" : 510 + "id" : 534 }, { - "id" : 511 + "id" : 535 }, { - "id" : 759 + "id" : 608 }, { - "id" : 348 + "id" : 392 }, { "id" : 89 @@ -3438,96 +3355,89 @@ "id" : 169 }, { - "id" : 323 + "id" : 358 }, { - "id" : 472 + "id" : 566 }, { - "id" : 473 + "id" : 567 }, { - "id" : 474 + "id" : 568 }, { - "id" : 475 + "id" : 569 }, { - "id" : 476 + "id" : 570 }, { - "id" : 753 + "id" : 602 }, { - "id" : 754 + "id" : 603 + }, + { + "id" : 357 + }, + { + "id" : 503 + }, + { + "id" : 581 + }, + { + "id" : 504 }, { "id" : 321 }, { - "id" : 389 + "id" : 360 }, { - "id" : 737 + "id" : 361 }, { - "id" : 390 + "id" : 362 }, { - "id" : 281 + "id" : 363 }, { - "id" : 325 + "id" : 364 }, { - "id" : 325, - "damage" : 1 + "id" : 365 }, { - "id" : 325, - "damage" : 8 + "id" : 366 }, { - "id" : 325, - "damage" : 10 + "id" : 367 }, { - "id" : 325, - "damage" : 2 - }, - { - "id" : 325, + "id" : 506, "damage" : 3 }, { - "id" : 325, - "damage" : 4 - }, - { - "id" : 325, - "damage" : 5 - }, - { - "id" : 397, - "damage" : 3 - }, - { - "id" : 397, + "id" : 506, "damage" : 2 }, { - "id" : 397, + "id" : 506, "damage" : 4 }, { - "id" : 397, + "id" : 506, "damage" : 5 }, { - "id" : 397 + "id" : 506 }, { - "id" : 397, + "id" : 506, "damage" : 1 }, { @@ -3546,92 +3456,576 @@ "id" : 120 }, { - "id" : 263 + "id" : 302 }, { - "id" : 263, - "damage" : 1 + "id" : 303 }, { - "id" : 264 + "id" : 304 }, { - "id" : 452 + "id" : 559 }, { - "id" : 265 + "id" : 305 }, { - "id" : 752 + "id" : 601 }, { - "id" : 742 + "id" : 591 }, { - "id" : 371 + "id" : 423 }, { - "id" : 266 + "id" : 306 }, { - "id" : 388 + "id" : 502 }, { - "id" : 406 + "id" : 514 }, { - "id" : 337 + "id" : 382 }, { - "id" : 336 + "id" : 381 }, { - "id" : 405 + "id" : 513 }, { - "id" : 409 + "id" : 555 + }, + { + "id" : 539 + }, + { + "id" : 560 + }, + { + "id" : 561 + }, + { + "id" : 562 + }, + { + "id" : 564 + }, + { + "id" : 326 + }, + { + "id" : 327 + }, + { + "id" : 356 + }, + { + "id" : 328 + }, + { + "id" : 379 + }, + { + "id" : 519 + }, + { + "id" : 518 + }, + { + "id" : 499 + }, + { + "id" : 421 + }, + { + "id" : 427 + }, + { + "id" : 428 + }, + { + "id" : 426 + }, + { + "id" : 550 + }, + { + "id" : 556 }, { "id" : 422 }, { - "id" : 465 + "id" : 386 }, { - "id" : 467 + "id" : 420 }, { - "id" : 468 + "id" : 431 }, { - "id" : 470 + "id" : 508 }, { - "id" : 287 + "id" : 208 }, { - "id" : 288 + "id" : 615 }, { - "id" : 318 - }, - { - "id" : 289 - }, - { - "id" : 334 - }, - { - "id" : 415 - }, - { - "id" : 414 + "id" : 384 }, { "id" : 385 }, { - "id" : 369 + "id" : 500 + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAQAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAQAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAQAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAQAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAQAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQFAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQFAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQFAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQGAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQGAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQGAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQHAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQHAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQHAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQIAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAQAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAUAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAQAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAUAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAQAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAUAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQMAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQMAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQNAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQNAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQOAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQOAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQOAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAQAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAUAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQQAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQRAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQRAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQRAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQSAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQSAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQSAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAQAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAUAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQUAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQUAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQVAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQWAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQXAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQXAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQXAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQYAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQYAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQYAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQZAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQZAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQaAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQbAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQcAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAQAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAUAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQeAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQeAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQeAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQfAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQfAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQfAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQgAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQhAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAQAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQjAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQjAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQjAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQkAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQkAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQkAAIDAGx2bAMAAAA=" + }, + { + "id" : 373 + }, + { + "id" : 376 + }, + { + "id" : 374 + }, + { + "id" : 375 }, { "id" : 377 @@ -3639,496 +4033,6 @@ { "id" : 378 }, - { - "id" : 376 - }, - { - "id" : 437 - }, - { - "id" : 445 - }, - { - "id" : 370 - }, - { - "id" : 341 - }, - { - "id" : 368 - }, - { - "id" : 381 - }, - { - "id" : 399 - }, - { - "id" : 208 - }, - { - "id" : 426 - }, - { - "id" : 339 - }, - { - "id" : 340 - }, - { - "id" : 386 - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZAAAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZAAAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZAAAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBAACAgBpZAAAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZAEAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZAEAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZAEAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBAACAgBpZAEAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZAIAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZAIAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZAIAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBAACAgBpZAIAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZAMAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZAMAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZAMAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBAACAgBpZAMAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZAQAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZAQAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZAQAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBAACAgBpZAQAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZAUAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZAUAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZAUAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZAYAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZAYAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZAYAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZAcAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZAcAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZAcAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZAgAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZAkAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZAkAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZAkAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBAACAgBpZAkAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBQACAgBpZAkAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZAoAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZAoAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZAoAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBAACAgBpZAoAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBQACAgBpZAoAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZAsAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZAsAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZAsAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBAACAgBpZAsAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBQACAgBpZAsAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZAwAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZAwAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZA0AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZA0AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZA4AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZA4AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZA4AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZA8AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZA8AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZA8AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBAACAgBpZA8AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBQACAgBpZA8AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZBAAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZBEAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZBEAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZBEAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZBIAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZBIAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZBIAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZBMAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZBMAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZBMAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBAACAgBpZBMAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBQACAgBpZBMAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZBQAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZBQAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZBUAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZBYAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZBcAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZBcAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZBcAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZBgAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZBgAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZBgAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZBkAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZBkAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZBoAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZBsAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZBwAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZB0AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZB0AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZB0AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBAACAgBpZB0AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBQACAgBpZB0AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZB4AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZB4AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZB4AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZB8AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZB8AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZB8AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZCAAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZCEAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZCIAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZCIAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZCIAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBAACAgBpZCIAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZCMAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZCMAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZCMAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZCQAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZCQAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZCQAAAA=" - }, - { - "id" : 333 - }, - { - "id" : 333, - "damage" : 1 - }, - { - "id" : 333, - "damage" : 2 - }, - { - "id" : 333, - "damage" : 3 - }, - { - "id" : 333, - "damage" : 4 - }, - { - "id" : 333, - "damage" : 5 - }, { "id" : 66 }, @@ -4142,19 +4046,19 @@ "id" : 126 }, { - "id" : 328 + "id" : 368 }, { - "id" : 342 + "id" : 387 }, { - "id" : 408 + "id" : 516 }, { - "id" : 407 + "id" : 515 }, { - "id" : 331 + "id" : 371 }, { "id" : 152 @@ -4241,13 +4145,13 @@ "id" : 151 }, { - "id" : 356 + "id" : 417 }, { - "id" : 404 + "id" : 512 }, { - "id" : 410 + "id" : 517 }, { "id" : 125, @@ -4269,252 +4173,255 @@ "id" : 46 }, { - "id" : 421 + "id" : 538 }, { "id" : -204 }, { - "id" : 446 + "id" : 557 }, { - "id" : 446, + "id" : 557, "damage" : 8 }, { - "id" : 446, + "id" : 557, "damage" : 7 }, { - "id" : 446, + "id" : 557, "damage" : 15 }, { - "id" : 446, + "id" : 557, "damage" : 12 }, { - "id" : 446, + "id" : 557, "damage" : 14 }, { - "id" : 446, + "id" : 557, "damage" : 1 }, { - "id" : 446, + "id" : 557, "damage" : 4 }, { - "id" : 446, + "id" : 557, "damage" : 5 }, { - "id" : 446, + "id" : 557, "damage" : 13 }, { - "id" : 446, + "id" : 557, "damage" : 9 }, { - "id" : 446, + "id" : 557, "damage" : 3 }, { - "id" : 446, + "id" : 557, "damage" : 11 }, { - "id" : 446, + "id" : 557, "damage" : 10 }, { - "id" : 446, + "id" : 557, "damage" : 2 }, { - "id" : 446, + "id" : 557, "damage" : 6 }, { - "id" : 446, + "id" : 557, "damage" : 15, "nbt_b64" : "CgAAAwQAVHlwZQEAAAAA" }, { - "id" : 434 + "id" : 572 }, { - "id" : 434, - "damage" : 1 + "id" : 573 }, { - "id" : 434, - "damage" : 2 + "id" : 571 }, { - "id" : 434, - "damage" : 3 + "id" : 574 }, { - "id" : 434, - "damage" : 4 + "id" : 575 }, { - "id" : 434, - "damage" : 5 + "id" : 576 }, { - "id" : 434, - "damage" : 6 + "id" : 577 }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMAAAAAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwAAAAAAAQYARmxpZ2h0AQAA" }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMKAQAAAAcNAEZpcmV3b3JrQ29sb3IBAAAAAAEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAABwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMKAQAAAAcNAEZpcmV3b3JrQ29sb3IBAAAACAEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAIBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMKAQAAAAcNAEZpcmV3b3JrQ29sb3IBAAAABwEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAHBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMKAQAAAAcNAEZpcmV3b3JrQ29sb3IBAAAADwEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAPBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMKAQAAAAcNAEZpcmV3b3JrQ29sb3IBAAAADAEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAMBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMKAQAAAAcNAEZpcmV3b3JrQ29sb3IBAAAADgEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAOBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMKAQAAAAcNAEZpcmV3b3JrQ29sb3IBAAAAAQEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAABBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMKAQAAAAcNAEZpcmV3b3JrQ29sb3IBAAAABAEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAEBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMKAQAAAAcNAEZpcmV3b3JrQ29sb3IBAAAABQEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAFBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMKAQAAAAcNAEZpcmV3b3JrQ29sb3IBAAAADQEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAANBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMKAQAAAAcNAEZpcmV3b3JrQ29sb3IBAAAACQEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAJBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMKAQAAAAcNAEZpcmV3b3JrQ29sb3IBAAAAAwEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAADBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMKAQAAAAcNAEZpcmV3b3JrQ29sb3IBAAAACwEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAALBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMKAQAAAAcNAEZpcmV3b3JrQ29sb3IBAAAACgEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAKBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMKAQAAAAcNAEZpcmV3b3JrQ29sb3IBAAAAAgEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAACBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMKAQAAAAcNAEZpcmV3b3JrQ29sb3IBAAAABgEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAGBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" }, { - "id" : 402, - "nbt_b64" : "CgAAAwsAY3VzdG9tQ29sb3IhHR3/Cg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAAEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAA=" + "id" : 510, + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yIR0d/wA=" }, { - "id" : 402, + "id" : 510, "damage" : 8, - "nbt_b64" : "CgAAAwsAY3VzdG9tQ29sb3JST0f/Cg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACAEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAA=" + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yUk9H/wA=" }, { - "id" : 402, + "id" : 510, "damage" : 7, - "nbt_b64" : "CgAAAwsAY3VzdG9tQ29sb3KXnZ3/Cg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABwEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAA=" + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yl52d/wA=" }, { - "id" : 402, + "id" : 510, "damage" : 15, - "nbt_b64" : "CgAAAwsAY3VzdG9tQ29sb3Lw8PD/Cg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADwEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAA=" + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9y8PDw/wA=" }, { - "id" : 402, + "id" : 510, "damage" : 12, - "nbt_b64" : "CgAAAwsAY3VzdG9tQ29sb3Laszr/Cg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADAEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAA=" + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9y2rM6/wA=" }, { - "id" : 402, + "id" : 510, "damage" : 14, - "nbt_b64" : "CgAAAwsAY3VzdG9tQ29sb3IdgPn/Cg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADgEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAA=" + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yHYD5/wA=" }, { - "id" : 402, + "id" : 510, "damage" : 1, - "nbt_b64" : "CgAAAwsAY3VzdG9tQ29sb3ImLrD/Cg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAQEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAA=" + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yJi6w/wA=" }, { - "id" : 402, + "id" : 510, "damage" : 4, - "nbt_b64" : "CgAAAwsAY3VzdG9tQ29sb3KqRDz/Cg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABAEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAA=" + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yqkQ8/wA=" }, { - "id" : 402, + "id" : 510, "damage" : 5, - "nbt_b64" : "CgAAAwsAY3VzdG9tQ29sb3K4Mon/Cg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABQEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAA=" + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yuDKJ/wA=" }, { - "id" : 402, + "id" : 510, "damage" : 13, - "nbt_b64" : "CgAAAwsAY3VzdG9tQ29sb3K9Tsf/Cg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADQEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAA=" + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yvU7H/wA=" }, { - "id" : 402, + "id" : 510, "damage" : 9, - "nbt_b64" : "CgAAAwsAY3VzdG9tQ29sb3Kqi/P/Cg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACQEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAA=" + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yqovz/wA=" }, { - "id" : 402, + "id" : 510, "damage" : 3, - "nbt_b64" : "CgAAAwsAY3VzdG9tQ29sb3IyVIP/Cg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAwEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAA=" + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yMlSD/wA=" }, { - "id" : 402, + "id" : 510, "damage" : 11, - "nbt_b64" : "CgAAAwsAY3VzdG9tQ29sb3I92P7/Cg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACwEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAA=" + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yPdj+/wA=" }, { - "id" : 402, + "id" : 510, "damage" : 10, - "nbt_b64" : "CgAAAwsAY3VzdG9tQ29sb3Ifx4D/Cg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACgEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAA=" + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yH8eA/wA=" }, { - "id" : 402, + "id" : 510, "damage" : 2, - "nbt_b64" : "CgAAAwsAY3VzdG9tQ29sb3IWfF7/Cg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAgEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAA=" + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yFnxe/wA=" }, { - "id" : 402, + "id" : 510, "damage" : 6, - "nbt_b64" : "CgAAAwsAY3VzdG9tQ29sb3KcnBb/Cg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABgEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAA=" + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9ynJwW/wA=" + }, + { + "id" : 607 + }, + { + "id" : -239 + }, + { + "id" : 590 } ] } \ No newline at end of file diff --git a/connector/src/main/resources/bedrock/legacy_block_ids.json b/connector/src/main/resources/bedrock/legacy_block_ids.json deleted file mode 100644 index 27594915..00000000 --- a/connector/src/main/resources/bedrock/legacy_block_ids.json +++ /dev/null @@ -1,555 +0,0 @@ -{ - "minecraft:air" : 0, - "minecraft:stone" : 1, - "minecraft:grass" : 2, - "minecraft:dirt" : 3, - "minecraft:cobblestone" : 4, - "minecraft:planks" : 5, - "minecraft:sapling" : 6, - "minecraft:bedrock" : 7, - "minecraft:flowing_water" : 8, - "minecraft:water" : 9, - "minecraft:flowing_lava" : 10, - "minecraft:lava" : 11, - "minecraft:sand" : 12, - "minecraft:gravel" : 13, - "minecraft:gold_ore" : 14, - "minecraft:iron_ore" : 15, - "minecraft:coal_ore" : 16, - "minecraft:log" : 17, - "minecraft:leaves" : 18, - "minecraft:sponge" : 19, - "minecraft:glass" : 20, - "minecraft:lapis_ore" : 21, - "minecraft:lapis_block" : 22, - "minecraft:dispenser" : 23, - "minecraft:sandstone" : 24, - "minecraft:noteblock" : 25, - "minecraft:bed" : 26, - "minecraft:golden_rail" : 27, - "minecraft:detector_rail" : 28, - "minecraft:sticky_piston" : 29, - "minecraft:web" : 30, - "minecraft:tallgrass" : 31, - "minecraft:deadbush" : 32, - "minecraft:piston" : 33, - "minecraft:pistonArmCollision" : 34, - "minecraft:wool" : 35, - "minecraft:element_0" : 36, - "minecraft:yellow_flower" : 37, - "minecraft:red_flower" : 38, - "minecraft:brown_mushroom" : 39, - "minecraft:red_mushroom" : 40, - "minecraft:gold_block" : 41, - "minecraft:iron_block" : 42, - "minecraft:double_stone_slab" : 43, - "minecraft:stone_slab" : 44, - "minecraft:brick_block" : 45, - "minecraft:tnt" : 46, - "minecraft:bookshelf" : 47, - "minecraft:mossy_cobblestone" : 48, - "minecraft:obsidian" : 49, - "minecraft:torch" : 50, - "minecraft:fire" : 51, - "minecraft:mob_spawner" : 52, - "minecraft:oak_stairs" : 53, - "minecraft:chest" : 54, - "minecraft:redstone_wire" : 55, - "minecraft:diamond_ore" : 56, - "minecraft:diamond_block" : 57, - "minecraft:crafting_table" : 58, - "minecraft:wheat" : 59, - "minecraft:farmland" : 60, - "minecraft:furnace" : 61, - "minecraft:lit_furnace" : 62, - "minecraft:standing_sign" : 63, - "minecraft:wooden_door" : 64, - "minecraft:ladder" : 65, - "minecraft:rail" : 66, - "minecraft:stone_stairs" : 67, - "minecraft:wall_sign" : 68, - "minecraft:lever" : 69, - "minecraft:stone_pressure_plate" : 70, - "minecraft:iron_door" : 71, - "minecraft:wooden_pressure_plate" : 72, - "minecraft:redstone_ore" : 73, - "minecraft:lit_redstone_ore" : 74, - "minecraft:unlit_redstone_torch" : 75, - "minecraft:redstone_torch" : 76, - "minecraft:stone_button" : 77, - "minecraft:snow_layer" : 78, - "minecraft:ice" : 79, - "minecraft:snow" : 80, - "minecraft:cactus" : 81, - "minecraft:clay" : 82, - "minecraft:reeds" : 83, - "minecraft:jukebox" : 84, - "minecraft:fence" : 85, - "minecraft:pumpkin" : 86, - "minecraft:netherrack" : 87, - "minecraft:soul_sand" : 88, - "minecraft:glowstone" : 89, - "minecraft:portal" : 90, - "minecraft:lit_pumpkin" : 91, - "minecraft:cake" : 92, - "minecraft:unpowered_repeater" : 93, - "minecraft:powered_repeater" : 94, - "minecraft:invisibleBedrock" : 95, - "minecraft:trapdoor" : 96, - "minecraft:monster_egg" : 97, - "minecraft:stonebrick" : 98, - "minecraft:brown_mushroom_block" : 99, - "minecraft:red_mushroom_block" : 100, - "minecraft:iron_bars" : 101, - "minecraft:glass_pane" : 102, - "minecraft:melon_block" : 103, - "minecraft:pumpkin_stem" : 104, - "minecraft:melon_stem" : 105, - "minecraft:vine" : 106, - "minecraft:fence_gate" : 107, - "minecraft:brick_stairs" : 108, - "minecraft:stone_brick_stairs" : 109, - "minecraft:mycelium" : 110, - "minecraft:waterlily" : 111, - "minecraft:nether_brick" : 112, - "minecraft:nether_brick_fence" : 113, - "minecraft:nether_brick_stairs" : 114, - "minecraft:nether_wart" : 115, - "minecraft:enchanting_table" : 116, - "minecraft:brewing_stand" : 117, - "minecraft:cauldron" : 118, - "minecraft:end_portal" : 119, - "minecraft:end_portal_frame" : 120, - "minecraft:end_stone" : 121, - "minecraft:dragon_egg" : 122, - "minecraft:redstone_lamp" : 123, - "minecraft:lit_redstone_lamp" : 124, - "minecraft:dropper" : 125, - "minecraft:activator_rail" : 126, - "minecraft:cocoa" : 127, - "minecraft:sandstone_stairs" : 128, - "minecraft:emerald_ore" : 129, - "minecraft:ender_chest" : 130, - "minecraft:tripwire_hook" : 131, - "minecraft:tripWire" : 132, - "minecraft:emerald_block" : 133, - "minecraft:spruce_stairs" : 134, - "minecraft:birch_stairs" : 135, - "minecraft:jungle_stairs" : 136, - "minecraft:command_block" : 137, - "minecraft:beacon" : 138, - "minecraft:cobblestone_wall" : 139, - "minecraft:flower_pot" : 140, - "minecraft:carrots" : 141, - "minecraft:potatoes" : 142, - "minecraft:wooden_button" : 143, - "minecraft:skull" : 144, - "minecraft:anvil" : 145, - "minecraft:trapped_chest" : 146, - "minecraft:light_weighted_pressure_plate" : 147, - "minecraft:heavy_weighted_pressure_plate" : 148, - "minecraft:unpowered_comparator" : 149, - "minecraft:powered_comparator" : 150, - "minecraft:daylight_detector" : 151, - "minecraft:redstone_block" : 152, - "minecraft:quartz_ore" : 153, - "minecraft:hopper" : 154, - "minecraft:quartz_block" : 155, - "minecraft:quartz_stairs" : 156, - "minecraft:double_wooden_slab" : 157, - "minecraft:wooden_slab" : 158, - "minecraft:stained_hardened_clay" : 159, - "minecraft:stained_glass_pane" : 160, - "minecraft:leaves2" : 161, - "minecraft:log2" : 162, - "minecraft:acacia_stairs" : 163, - "minecraft:dark_oak_stairs" : 164, - "minecraft:slime" : 165, - "minecraft:iron_trapdoor" : 167, - "minecraft:prismarine" : 168, - "minecraft:seaLantern" : 169, - "minecraft:hay_block" : 170, - "minecraft:carpet" : 171, - "minecraft:hardened_clay" : 172, - "minecraft:coal_block" : 173, - "minecraft:packed_ice" : 174, - "minecraft:double_plant" : 175, - "minecraft:standing_banner" : 176, - "minecraft:wall_banner" : 177, - "minecraft:daylight_detector_inverted" : 178, - "minecraft:red_sandstone" : 179, - "minecraft:red_sandstone_stairs" : 180, - "minecraft:double_stone_slab2" : 181, - "minecraft:stone_slab2" : 182, - "minecraft:spruce_fence_gate" : 183, - "minecraft:birch_fence_gate" : 184, - "minecraft:jungle_fence_gate" : 185, - "minecraft:dark_oak_fence_gate" : 186, - "minecraft:acacia_fence_gate" : 187, - "minecraft:repeating_command_block" : 188, - "minecraft:chain_command_block" : 189, - "minecraft:hard_glass_pane" : 190, - "minecraft:hard_stained_glass_pane" : 191, - "minecraft:chemical_heat" : 192, - "minecraft:spruce_door" : 193, - "minecraft:birch_door" : 194, - "minecraft:jungle_door" : 195, - "minecraft:acacia_door" : 196, - "minecraft:dark_oak_door" : 197, - "minecraft:grass_path" : 198, - "minecraft:frame" : 199, - "minecraft:chorus_flower" : 200, - "minecraft:purpur_block" : 201, - "minecraft:colored_torch_rg" : 202, - "minecraft:purpur_stairs" : 203, - "minecraft:colored_torch_bp" : 204, - "minecraft:undyed_shulker_box" : 205, - "minecraft:end_bricks" : 206, - "minecraft:frosted_ice" : 207, - "minecraft:end_rod" : 208, - "minecraft:end_gateway" : 209, - "minecraft:allow" : 210, - "minecraft:deny" : 211, - "minecraft:border_block" : 212, - "minecraft:magma" : 213, - "minecraft:nether_wart_block" : 214, - "minecraft:red_nether_brick" : 215, - "minecraft:bone_block" : 216, - "minecraft:structure_void" : 217, - "minecraft:shulker_box" : 218, - "minecraft:purple_glazed_terracotta" : 219, - "minecraft:white_glazed_terracotta" : 220, - "minecraft:orange_glazed_terracotta" : 221, - "minecraft:magenta_glazed_terracotta" : 222, - "minecraft:light_blue_glazed_terracotta" : 223, - "minecraft:yellow_glazed_terracotta" : 224, - "minecraft:lime_glazed_terracotta" : 225, - "minecraft:pink_glazed_terracotta" : 226, - "minecraft:gray_glazed_terracotta" : 227, - "minecraft:silver_glazed_terracotta" : 228, - "minecraft:cyan_glazed_terracotta" : 229, - "minecraft:blue_glazed_terracotta" : 231, - "minecraft:brown_glazed_terracotta" : 232, - "minecraft:green_glazed_terracotta" : 233, - "minecraft:red_glazed_terracotta" : 234, - "minecraft:black_glazed_terracotta" : 235, - "minecraft:concrete" : 236, - "minecraft:concretePowder" : 237, - "minecraft:chemistry_table" : 238, - "minecraft:underwater_torch" : 239, - "minecraft:chorus_plant" : 240, - "minecraft:stained_glass" : 241, - "minecraft:camera" : 242, - "minecraft:podzol" : 243, - "minecraft:beetroot" : 244, - "minecraft:stonecutter" : 245, - "minecraft:glowingobsidian" : 246, - "minecraft:netherreactor" : 247, - "minecraft:info_update" : 248, - "minecraft:info_update2" : 249, - "minecraft:movingBlock" : 250, - "minecraft:observer" : 251, - "minecraft:structure_block" : 252, - "minecraft:hard_glass" : 253, - "minecraft:hard_stained_glass" : 254, - "minecraft:reserved6" : 255, - "minecraft:prismarine_stairs" : 257, - "minecraft:dark_prismarine_stairs" : 258, - "minecraft:prismarine_bricks_stairs" : 259, - "minecraft:stripped_spruce_log" : 260, - "minecraft:stripped_birch_log" : 261, - "minecraft:stripped_jungle_log" : 262, - "minecraft:stripped_acacia_log" : 263, - "minecraft:stripped_dark_oak_log" : 264, - "minecraft:stripped_oak_log" : 265, - "minecraft:blue_ice" : 266, - "minecraft:element_1" : 267, - "minecraft:element_2" : 268, - "minecraft:element_3" : 269, - "minecraft:element_4" : 270, - "minecraft:element_5" : 271, - "minecraft:element_6" : 272, - "minecraft:element_7" : 273, - "minecraft:element_8" : 274, - "minecraft:element_9" : 275, - "minecraft:element_10" : 276, - "minecraft:element_11" : 277, - "minecraft:element_12" : 278, - "minecraft:element_13" : 279, - "minecraft:element_14" : 280, - "minecraft:element_15" : 281, - "minecraft:element_16" : 282, - "minecraft:element_17" : 283, - "minecraft:element_18" : 284, - "minecraft:element_19" : 285, - "minecraft:element_20" : 286, - "minecraft:element_21" : 287, - "minecraft:element_22" : 288, - "minecraft:element_23" : 289, - "minecraft:element_24" : 290, - "minecraft:element_25" : 291, - "minecraft:element_26" : 292, - "minecraft:element_27" : 293, - "minecraft:element_28" : 294, - "minecraft:element_29" : 295, - "minecraft:element_30" : 296, - "minecraft:element_31" : 297, - "minecraft:element_32" : 298, - "minecraft:element_33" : 299, - "minecraft:element_34" : 300, - "minecraft:element_35" : 301, - "minecraft:element_36" : 302, - "minecraft:element_37" : 303, - "minecraft:element_38" : 304, - "minecraft:element_39" : 305, - "minecraft:element_40" : 306, - "minecraft:element_41" : 307, - "minecraft:element_42" : 308, - "minecraft:element_43" : 309, - "minecraft:element_44" : 310, - "minecraft:element_45" : 311, - "minecraft:element_46" : 312, - "minecraft:element_47" : 313, - "minecraft:element_48" : 314, - "minecraft:element_49" : 315, - "minecraft:element_50" : 316, - "minecraft:element_51" : 317, - "minecraft:element_52" : 318, - "minecraft:element_53" : 319, - "minecraft:element_54" : 320, - "minecraft:element_55" : 321, - "minecraft:element_56" : 322, - "minecraft:element_57" : 323, - "minecraft:element_58" : 324, - "minecraft:element_59" : 325, - "minecraft:element_60" : 326, - "minecraft:element_61" : 327, - "minecraft:element_62" : 328, - "minecraft:element_63" : 329, - "minecraft:element_64" : 330, - "minecraft:element_65" : 331, - "minecraft:element_66" : 332, - "minecraft:element_67" : 333, - "minecraft:element_68" : 334, - "minecraft:element_69" : 335, - "minecraft:element_70" : 336, - "minecraft:element_71" : 337, - "minecraft:element_72" : 338, - "minecraft:element_73" : 339, - "minecraft:element_74" : 340, - "minecraft:element_75" : 341, - "minecraft:element_76" : 342, - "minecraft:element_77" : 343, - "minecraft:element_78" : 344, - "minecraft:element_79" : 345, - "minecraft:element_80" : 346, - "minecraft:element_81" : 347, - "minecraft:element_82" : 348, - "minecraft:element_83" : 349, - "minecraft:element_84" : 350, - "minecraft:element_85" : 351, - "minecraft:element_86" : 352, - "minecraft:element_87" : 353, - "minecraft:element_88" : 354, - "minecraft:element_89" : 355, - "minecraft:element_90" : 356, - "minecraft:element_91" : 357, - "minecraft:element_92" : 358, - "minecraft:element_93" : 359, - "minecraft:element_94" : 360, - "minecraft:element_95" : 361, - "minecraft:element_96" : 362, - "minecraft:element_97" : 363, - "minecraft:element_98" : 364, - "minecraft:element_99" : 365, - "minecraft:element_100" : 366, - "minecraft:element_101" : 367, - "minecraft:element_102" : 368, - "minecraft:element_103" : 369, - "minecraft:element_104" : 370, - "minecraft:element_105" : 371, - "minecraft:element_106" : 372, - "minecraft:element_107" : 373, - "minecraft:element_108" : 374, - "minecraft:element_109" : 375, - "minecraft:element_110" : 376, - "minecraft:element_111" : 377, - "minecraft:element_112" : 378, - "minecraft:element_113" : 379, - "minecraft:element_114" : 380, - "minecraft:element_115" : 381, - "minecraft:element_116" : 382, - "minecraft:element_117" : 383, - "minecraft:element_118" : 384, - "minecraft:seagrass" : 385, - "minecraft:coral" : 386, - "minecraft:coral_block" : 387, - "minecraft:coral_fan" : 388, - "minecraft:coral_fan_dead" : 389, - "minecraft:coral_fan_hang" : 390, - "minecraft:coral_fan_hang2" : 391, - "minecraft:coral_fan_hang3" : 392, - "minecraft:kelp" : 393, - "minecraft:dried_kelp_block" : 394, - "minecraft:acacia_button" : 395, - "minecraft:birch_button" : 396, - "minecraft:dark_oak_button" : 397, - "minecraft:jungle_button" : 398, - "minecraft:spruce_button" : 399, - "minecraft:acacia_trapdoor" : 400, - "minecraft:birch_trapdoor" : 401, - "minecraft:dark_oak_trapdoor" : 402, - "minecraft:jungle_trapdoor" : 403, - "minecraft:spruce_trapdoor" : 404, - "minecraft:acacia_pressure_plate" : 405, - "minecraft:birch_pressure_plate" : 406, - "minecraft:dark_oak_pressure_plate" : 407, - "minecraft:jungle_pressure_plate" : 408, - "minecraft:spruce_pressure_plate" : 409, - "minecraft:carved_pumpkin" : 410, - "minecraft:sea_pickle" : 411, - "minecraft:conduit" : 412, - "minecraft:turtle_egg" : 414, - "minecraft:bubble_column" : 415, - "minecraft:barrier" : 416, - "minecraft:stone_slab3" : 417, - "minecraft:bamboo" : 418, - "minecraft:bamboo_sapling" : 419, - "minecraft:scaffolding" : 420, - "minecraft:stone_slab4" : 421, - "minecraft:double_stone_slab3" : 422, - "minecraft:double_stone_slab4" : 423, - "minecraft:granite_stairs" : 424, - "minecraft:diorite_stairs" : 425, - "minecraft:andesite_stairs" : 426, - "minecraft:polished_granite_stairs" : 427, - "minecraft:polished_diorite_stairs" : 428, - "minecraft:polished_andesite_stairs" : 429, - "minecraft:mossy_stone_brick_stairs" : 430, - "minecraft:smooth_red_sandstone_stairs" : 431, - "minecraft:smooth_sandstone_stairs" : 432, - "minecraft:end_brick_stairs" : 433, - "minecraft:mossy_cobblestone_stairs" : 434, - "minecraft:normal_stone_stairs" : 435, - "minecraft:spruce_standing_sign" : 436, - "minecraft:spruce_wall_sign" : 437, - "minecraft:smooth_stone" : 438, - "minecraft:red_nether_brick_stairs" : 439, - "minecraft:smooth_quartz_stairs" : 440, - "minecraft:birch_standing_sign" : 441, - "minecraft:birch_wall_sign" : 442, - "minecraft:jungle_standing_sign" : 443, - "minecraft:jungle_wall_sign" : 444, - "minecraft:acacia_standing_sign" : 445, - "minecraft:acacia_wall_sign" : 446, - "minecraft:darkoak_standing_sign" : 447, - "minecraft:darkoak_wall_sign" : 448, - "minecraft:lectern" : 449, - "minecraft:grindstone" : 450, - "minecraft:blast_furnace" : 451, - "minecraft:stonecutter_block" : 452, - "minecraft:smoker" : 453, - "minecraft:lit_smoker" : 454, - "minecraft:cartography_table" : 455, - "minecraft:fletching_table" : 456, - "minecraft:smithing_table" : 457, - "minecraft:barrel" : 458, - "minecraft:loom" : 459, - "minecraft:bell" : 461, - "minecraft:sweet_berry_bush" : 462, - "minecraft:lantern" : 463, - "minecraft:campfire" : 464, - "minecraft:lava_cauldron" : 465, - "minecraft:jigsaw" : 466, - "minecraft:wood" : 467, - "minecraft:composter" : 468, - "minecraft:lit_blast_furnace" : 469, - "minecraft:light_block" : 470, - "minecraft:wither_rose" : 471, - "minecraft:stickyPistonArmCollision" : 472, - "minecraft:bee_nest" : 473, - "minecraft:beehive" : 474, - "minecraft:honey_block" : 475, - "minecraft:honeycomb_block" : 476, - "minecraft:lodestone" : 477, - "minecraft:crimson_roots" : 478, - "minecraft:warped_roots" : 479, - "minecraft:crimson_stem" : 480, - "minecraft:warped_stem" : 481, - "minecraft:warped_wart_block" : 482, - "minecraft:crimson_fungus" : 483, - "minecraft:warped_fungus" : 484, - "minecraft:shroomlight" : 485, - "minecraft:weeping_vines" : 486, - "minecraft:crimson_nylium" : 487, - "minecraft:warped_nylium" : 488, - "minecraft:basalt" : 489, - "minecraft:polished_basalt" : 490, - "minecraft:soul_soil" : 491, - "minecraft:soul_fire" : 492, - "minecraft:nether_sprouts" : 493, - "minecraft:target" : 494, - "minecraft:stripped_crimson_stem" : 495, - "minecraft:stripped_warped_stem" : 496, - "minecraft:crimson_planks" : 497, - "minecraft:warped_planks" : 498, - "minecraft:crimson_door" : 499, - "minecraft:warped_door" : 500, - "minecraft:crimson_trapdoor" : 501, - "minecraft:warped_trapdoor" : 502, - "minecraft:crimson_standing_sign" : 505, - "minecraft:warped_standing_sign" : 506, - "minecraft:crimson_wall_sign" : 507, - "minecraft:warped_wall_sign" : 508, - "minecraft:crimson_stairs" : 509, - "minecraft:warped_stairs" : 510, - "minecraft:crimson_fence" : 511, - "minecraft:warped_fence" : 512, - "minecraft:crimson_fence_gate" : 513, - "minecraft:warped_fence_gate" : 514, - "minecraft:crimson_button" : 515, - "minecraft:warped_button" : 516, - "minecraft:crimson_pressure_plate" : 517, - "minecraft:warped_pressure_plate" : 518, - "minecraft:crimson_slab" : 519, - "minecraft:warped_slab" : 520, - "minecraft:crimson_double_slab" : 521, - "minecraft:warped_double_slab" : 522, - "minecraft:soul_torch" : 523, - "minecraft:soul_lantern" : 524, - "minecraft:netherite_block" : 525, - "minecraft:ancient_debris" : 526, - "minecraft:respawn_anchor" : 527, - "minecraft:blackstone" : 528, - "minecraft:polished_blackstone_bricks" : 529, - "minecraft:polished_blackstone_brick_stairs" : 530, - "minecraft:blackstone_stairs" : 531, - "minecraft:blackstone_wall" : 532, - "minecraft:polished_blackstone_brick_wall" : 533, - "minecraft:chiseled_polished_blackstone" : 534, - "minecraft:cracked_polished_blackstone_bricks" : 535, - "minecraft:gilded_blackstone" : 536, - "minecraft:blackstone_slab" : 537, - "minecraft:blackstone_double_slab" : 538, - "minecraft:polished_blackstone_brick_slab" : 539, - "minecraft:polished_blackstone_brick_double_slab" : 540, - "minecraft:chain" : 541, - "minecraft:twisting_vines" : 542, - "minecraft:nether_gold_ore" : 543, - "minecraft:crying_obsidian" : 544, - "minecraft:soul_campfire" : 545, - "minecraft:polished_blackstone" : 546, - "minecraft:polished_blackstone_stairs" : 547, - "minecraft:polished_blackstone_slab" : 548, - "minecraft:polished_blackstone_double_slab" : 549, - "minecraft:polished_blackstone_pressure_plate" : 550, - "minecraft:polished_blackstone_button" : 551, - "minecraft:polished_blackstone_wall" : 552, - "minecraft:warped_hyphae" : 553, - "minecraft:crimson_hyphae" : 554, - "minecraft:stripped_crimson_hyphae" : 555, - "minecraft:stripped_warped_hyphae" : 556, - "minecraft:chiseled_nether_bricks" : 557, - "minecraft:cracked_nether_bricks" : 558, - "minecraft:quartz_bricks" : 559 -} \ No newline at end of file diff --git a/connector/src/main/resources/bedrock/legacy_item_ids.json b/connector/src/main/resources/bedrock/legacy_item_ids.json deleted file mode 100644 index 3f22c91c..00000000 --- a/connector/src/main/resources/bedrock/legacy_item_ids.json +++ /dev/null @@ -1,255 +0,0 @@ -{ - "minecraft:iron_shovel" : 256, - "minecraft:iron_pickaxe" : 257, - "minecraft:iron_axe" : 258, - "minecraft:flint_and_steel" : 259, - "minecraft:apple" : 260, - "minecraft:bow" : 261, - "minecraft:arrow" : 262, - "minecraft:coal" : 263, - "minecraft:diamond" : 264, - "minecraft:iron_ingot" : 265, - "minecraft:gold_ingot" : 266, - "minecraft:iron_sword" : 267, - "minecraft:wooden_sword" : 268, - "minecraft:wooden_shovel" : 269, - "minecraft:wooden_pickaxe" : 270, - "minecraft:wooden_axe" : 271, - "minecraft:stone_sword" : 272, - "minecraft:stone_shovel" : 273, - "minecraft:stone_pickaxe" : 274, - "minecraft:stone_axe" : 275, - "minecraft:diamond_sword" : 276, - "minecraft:diamond_shovel" : 277, - "minecraft:diamond_pickaxe" : 278, - "minecraft:diamond_axe" : 279, - "minecraft:stick" : 280, - "minecraft:bowl" : 281, - "minecraft:mushroom_stew" : 282, - "minecraft:golden_sword" : 283, - "minecraft:golden_shovel" : 284, - "minecraft:golden_pickaxe" : 285, - "minecraft:golden_axe" : 286, - "minecraft:string" : 287, - "minecraft:feather" : 288, - "minecraft:gunpowder" : 289, - "minecraft:wooden_hoe" : 290, - "minecraft:stone_hoe" : 291, - "minecraft:iron_hoe" : 292, - "minecraft:diamond_hoe" : 293, - "minecraft:golden_hoe" : 294, - "minecraft:wheat_seeds" : 295, - "minecraft:wheat" : 296, - "minecraft:bread" : 297, - "minecraft:leather_helmet" : 298, - "minecraft:leather_chestplate" : 299, - "minecraft:leather_leggings" : 300, - "minecraft:leather_boots" : 301, - "minecraft:chainmail_helmet" : 302, - "minecraft:chainmail_chestplate" : 303, - "minecraft:chainmail_leggings" : 304, - "minecraft:chainmail_boots" : 305, - "minecraft:iron_helmet" : 306, - "minecraft:iron_chestplate" : 307, - "minecraft:iron_leggings" : 308, - "minecraft:iron_boots" : 309, - "minecraft:diamond_helmet" : 310, - "minecraft:diamond_chestplate" : 311, - "minecraft:diamond_leggings" : 312, - "minecraft:diamond_boots" : 313, - "minecraft:golden_helmet" : 314, - "minecraft:golden_chestplate" : 315, - "minecraft:golden_leggings" : 316, - "minecraft:golden_boots" : 317, - "minecraft:flint" : 318, - "minecraft:porkchop" : 319, - "minecraft:cooked_porkchop" : 320, - "minecraft:painting" : 321, - "minecraft:golden_apple" : 322, - "minecraft:sign" : 323, - "minecraft:wooden_door" : 324, - "minecraft:bucket" : 325, - "minecraft:minecart" : 328, - "minecraft:saddle" : 329, - "minecraft:iron_door" : 330, - "minecraft:redstone" : 331, - "minecraft:snowball" : 332, - "minecraft:boat" : 333, - "minecraft:leather" : 334, - "minecraft:kelp" : 335, - "minecraft:brick" : 336, - "minecraft:clay_ball" : 337, - "minecraft:reeds" : 338, - "minecraft:paper" : 339, - "minecraft:book" : 340, - "minecraft:slime_ball" : 341, - "minecraft:chest_minecart" : 342, - "minecraft:egg" : 344, - "minecraft:compass" : 345, - "minecraft:fishing_rod" : 346, - "minecraft:clock" : 347, - "minecraft:glowstone_dust" : 348, - "minecraft:fish" : 349, - "minecraft:cooked_fish" : 350, - "minecraft:dye" : 351, - "minecraft:bone" : 352, - "minecraft:sugar" : 353, - "minecraft:cake" : 354, - "minecraft:bed" : 355, - "minecraft:repeater" : 356, - "minecraft:cookie" : 357, - "minecraft:map" : 358, - "minecraft:shears" : 359, - "minecraft:melon" : 360, - "minecraft:pumpkin_seeds" : 361, - "minecraft:melon_seeds" : 362, - "minecraft:beef" : 363, - "minecraft:cooked_beef" : 364, - "minecraft:chicken" : 365, - "minecraft:cooked_chicken" : 366, - "minecraft:rotten_flesh" : 367, - "minecraft:ender_pearl" : 368, - "minecraft:blaze_rod" : 369, - "minecraft:ghast_tear" : 370, - "minecraft:gold_nugget" : 371, - "minecraft:nether_wart" : 372, - "minecraft:potion" : 373, - "minecraft:glass_bottle" : 374, - "minecraft:spider_eye" : 375, - "minecraft:fermented_spider_eye" : 376, - "minecraft:blaze_powder" : 377, - "minecraft:magma_cream" : 378, - "minecraft:brewing_stand" : 379, - "minecraft:cauldron" : 380, - "minecraft:ender_eye" : 381, - "minecraft:speckled_melon" : 382, - "minecraft:spawn_egg" : 383, - "minecraft:experience_bottle" : 384, - "minecraft:fireball" : 385, - "minecraft:writable_book" : 386, - "minecraft:written_book" : 387, - "minecraft:emerald" : 388, - "minecraft:frame" : 389, - "minecraft:flower_pot" : 390, - "minecraft:carrot" : 391, - "minecraft:potato" : 392, - "minecraft:baked_potato" : 393, - "minecraft:poisonous_potato" : 394, - "minecraft:emptymap" : 395, - "minecraft:golden_carrot" : 396, - "minecraft:skull" : 397, - "minecraft:carrotonastick" : 398, - "minecraft:netherstar" : 399, - "minecraft:pumpkin_pie" : 400, - "minecraft:fireworks" : 401, - "minecraft:fireworkscharge" : 402, - "minecraft:enchanted_book" : 403, - "minecraft:comparator" : 404, - "minecraft:netherbrick" : 405, - "minecraft:quartz" : 406, - "minecraft:tnt_minecart" : 407, - "minecraft:hopper_minecart" : 408, - "minecraft:prismarine_shard" : 409, - "minecraft:hopper" : 410, - "minecraft:rabbit" : 411, - "minecraft:cooked_rabbit" : 412, - "minecraft:rabbit_stew" : 413, - "minecraft:rabbit_foot" : 414, - "minecraft:rabbit_hide" : 415, - "minecraft:horsearmorleather" : 416, - "minecraft:horsearmoriron" : 417, - "minecraft:horsearmorgold" : 418, - "minecraft:horsearmordiamond" : 419, - "minecraft:lead" : 420, - "minecraft:name_tag" : 421, - "minecraft:prismarine_crystals" : 422, - "minecraft:muttonraw" : 423, - "minecraft:muttoncooked" : 424, - "minecraft:armor_stand" : 425, - "minecraft:end_crystal" : 426, - "minecraft:spruce_door" : 427, - "minecraft:birch_door" : 428, - "minecraft:jungle_door" : 429, - "minecraft:acacia_door" : 430, - "minecraft:dark_oak_door" : 431, - "minecraft:chorus_fruit" : 432, - "minecraft:chorus_fruit_popped" : 433, - "minecraft:banner_pattern" : 434, - "minecraft:dragon_breath" : 437, - "minecraft:splash_potion" : 438, - "minecraft:lingering_potion" : 441, - "minecraft:sparkler" : 442, - "minecraft:command_block_minecart" : 443, - "minecraft:elytra" : 444, - "minecraft:shulker_shell" : 445, - "minecraft:banner" : 446, - "minecraft:medicine" : 447, - "minecraft:balloon" : 448, - "minecraft:rapid_fertilizer" : 449, - "minecraft:totem" : 450, - "minecraft:bleach" : 451, - "minecraft:iron_nugget" : 452, - "minecraft:ice_bomb" : 453, - "minecraft:trident" : 455, - "minecraft:beetroot" : 457, - "minecraft:beetroot_seeds" : 458, - "minecraft:beetroot_soup" : 459, - "minecraft:salmon" : 460, - "minecraft:clownfish" : 461, - "minecraft:pufferfish" : 462, - "minecraft:cooked_salmon" : 463, - "minecraft:dried_kelp" : 464, - "minecraft:nautilus_shell" : 465, - "minecraft:appleenchanted" : 466, - "minecraft:heart_of_the_sea" : 467, - "minecraft:turtle_shell_piece" : 468, - "minecraft:turtle_helmet" : 469, - "minecraft:phantom_membrane" : 470, - "minecraft:crossbow" : 471, - "minecraft:spruce_sign" : 472, - "minecraft:birch_sign" : 473, - "minecraft:jungle_sign" : 474, - "minecraft:acacia_sign" : 475, - "minecraft:darkoak_sign" : 476, - "minecraft:sweet_berries" : 477, - "minecraft:camera" : 498, - "minecraft:compound" : 499, - "minecraft:record_13" : 500, - "minecraft:record_cat" : 501, - "minecraft:record_blocks" : 502, - "minecraft:record_chirp" : 503, - "minecraft:record_far" : 504, - "minecraft:record_mall" : 505, - "minecraft:record_mellohi" : 506, - "minecraft:record_stal" : 507, - "minecraft:record_strad" : 508, - "minecraft:record_ward" : 509, - "minecraft:record_11" : 510, - "minecraft:record_wait" : 511, - "minecraft:shield" : 513, - "minecraft:campfire" : 720, - "minecraft:suspicious_stew" : 734, - "minecraft:honeycomb" : 736, - "minecraft:honey_bottle" : 737, - "minecraft:lodestonecompass" : 741, - "minecraft:netherite_ingot" : 742, - "minecraft:netherite_sword" : 743, - "minecraft:netherite_shovel" : 744, - "minecraft:netherite_pickaxe" : 745, - "minecraft:netherite_axe" : 746, - "minecraft:netherite_hoe" : 747, - "minecraft:netherite_helmet" : 748, - "minecraft:netherite_chestplate" : 749, - "minecraft:netherite_leggings" : 750, - "minecraft:netherite_boots" : 751, - "minecraft:netherite_scrap" : 752, - "minecraft:crimson_sign" : 753, - "minecraft:warped_sign" : 754, - "minecraft:crimson_door" : 755, - "minecraft:warped_door" : 756, - "minecraft:warped_fungus_on_a_stick" : 757, - "minecraft:chain" : 758, - "minecraft:record_pigstep" : 759, - "minecraft:nether_sprouts" : 760, - "minecraft:soul_campfire" : 801 -} \ No newline at end of file diff --git a/connector/src/main/resources/bedrock/runtime_block_states.dat b/connector/src/main/resources/bedrock/runtime_block_states.dat deleted file mode 100644 index feeeda664bdf469af04e3758e1a584b76bb71f34..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1063028 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 daWF8y=mGU<3(8pke_4.0.0 org.geysermc geyser-parent - parent + 1.2.0-SNAPSHOT pom Geyser Allows for players from Minecraft Bedrock Edition to join Minecraft Java Edition servers. @@ -71,19 +71,6 @@ - - - releases - opencollab-releases - https://repo.opencollab.dev/maven-releases - - - snapshots - opencollab-snapshots - https://repo.opencollab.dev/maven-snapshots - - - org.projectlombok From 003a1bef03b6aa68dc37199d994bc4fe6628bc4e Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Tue, 17 Nov 2020 12:54:09 -0500 Subject: [PATCH 116/205] Revert artifactory changes preventing compilation (#1555) --- Jenkinsfile | 22 +--------------------- pom.xml | 13 +++++++++++++ 2 files changed, 14 insertions(+), 21 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 96b189f2..7dfdaf30 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -26,27 +26,7 @@ pipeline { } steps { - rtMavenDeployer( - id: "maven-deployer", - serverId: "opencollab-artifactory", - releaseRepo: "maven-releases", - snapshotRepo: "maven-snapshots" - ) - rtMavenResolver( - id: "maven-resolver", - serverId: "opencollab-artifactory", - releaseRepo: "release", - snapshotRepo: "snapshot" - ) - rtMavenRun( - pom: 'pom.xml', - goals: 'javadoc:jar source:jar install -DskipTests', - deployerId: "maven-deployer", - resolverId: "maven-resolver" - ) - rtPublishBuildInfo( - serverId: "opencollab-artifactory" - ) + sh 'mvn javadoc:jar source:jar deploy -DskipTests' } } } diff --git a/pom.xml b/pom.xml index 011b320f..1b544f9e 100644 --- a/pom.xml +++ b/pom.xml @@ -71,6 +71,19 @@ + + + releases + opencollab-releases + https://repo.opencollab.dev/maven-releases + + + snapshots + opencollab-snapshots + https://repo.opencollab.dev/maven-snapshots + + + org.projectlombok From 99558b61a604d7c4c6ba85ec2bce563cdeb9f23a Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Tue, 17 Nov 2020 13:28:08 -0500 Subject: [PATCH 117/205] Print reason for disconnect when outdated (#1556) --- .../org/geysermc/connector/network/UpstreamPacketHandler.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java index e871b7f7..3c4dc821 100644 --- a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java @@ -61,9 +61,11 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { if (packetCodec == null) { if (loginPacket.getProtocolVersion() > BedrockProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) { // Too early to determine session locale + session.getConnector().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.outdated.server", BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion())); session.disconnect(LanguageUtils.getLocaleStringLog("geyser.network.outdated.server", BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion())); return true; } else if (loginPacket.getProtocolVersion() < BedrockProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) { + session.getConnector().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.outdated.client", BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion())); session.disconnect(LanguageUtils.getLocaleStringLog("geyser.network.outdated.client", BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion())); return true; } From 199778faea74fc9b7928ef3711d99f31bc19af18 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Wed, 18 Nov 2020 01:10:49 -0500 Subject: [PATCH 118/205] Fix regressions from 1.16.100 (#1558) * Fix regressions from 1.16.100 - Update mappings to fix recipe regressions and item differences - Villager trading NBT now prefers the String identifier and not the integer ID * Fix lodestone compass breaking --- .../connector/entity/ItemFrameEntity.java | 4 +- .../network/translators/item/ItemEntry.java | 3 +- .../translators/item/ItemRegistry.java | 40 +++++++------------ .../translators/item/ItemTranslator.java | 5 +-- .../translators/item/ToolItemEntry.java | 4 +- .../item/translators/CompassTranslator.java | 4 +- .../translators/nbt/CrossbowTranslator.java | 6 +-- .../nbt/ShulkerBoxItemTranslator.java | 3 +- .../java/world/JavaTradeListTranslator.java | 2 +- connector/src/main/resources/mappings | 2 +- 10 files changed, 28 insertions(+), 45 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java b/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java index 62fe3afe..972fa8d0 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java @@ -98,14 +98,12 @@ public class ItemFrameEntity extends Entity { ItemEntry itemEntry = ItemRegistry.getItem((ItemStack) entityMetadata.getValue()); NbtMapBuilder builder = NbtMap.builder(); - String blockName = ItemRegistry.getBedrockIdentifier(itemEntry); - builder.putByte("Count", (byte) itemData.getCount()); if (itemData.getTag() != null) { builder.put("tag", itemData.getTag().toBuilder().build()); } builder.putShort("Damage", itemData.getDamage()); - builder.putString("Name", blockName); + builder.putString("Name", itemEntry.getBedrockIdentifier()); NbtMapBuilder tag = getDefaultTag().toBuilder(); tag.put("Item", builder.build()); tag.putFloat("ItemDropChance", 1.0f); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemEntry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemEntry.java index 19f83cd9..3fcae17d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemEntry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemEntry.java @@ -34,9 +34,10 @@ import lombok.ToString; @ToString public class ItemEntry { - public static ItemEntry AIR = new ItemEntry("minecraft:air", 0, 0, 0, false); + public static ItemEntry AIR = new ItemEntry("minecraft:air", "minecraft:air", 0, 0, 0, false); private final String javaIdentifier; + private final String bedrockIdentifier; private final int javaId; private final int bedrockId; private final int bedrockData; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java index 3e026885..a4a2fb75 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java @@ -103,6 +103,9 @@ public class ItemRegistry { TypeReference> itemEntriesType = new TypeReference>() { }; + // Used to get the Bedrock namespaced ID (in instances where there are small differences) + Int2ObjectMap bedrockIdToIdentifier = new Int2ObjectOpenHashMap<>(); + List itemEntries; try { itemEntries = GeyserConnector.JSON_MAPPER.readValue(stream, itemEntriesType); @@ -114,6 +117,7 @@ public class ItemRegistry { for (JsonNode entry : itemEntries) { ITEMS.add(new StartGamePacket.ItemEntry(entry.get("name").textValue(), (short) entry.get("id").intValue())); + bedrockIdToIdentifier.put(entry.get("id").intValue(), entry.get("name").textValue()); if (entry.get("name").textValue().equals("minecraft:lodestone_compass")) { lodestoneCompassId = entry.get("id").intValue(); } @@ -132,28 +136,29 @@ public class ItemRegistry { Iterator> iterator = items.fields(); while (iterator.hasNext()) { Map.Entry entry = iterator.next(); + int bedrockId = entry.getValue().get("bedrock_id").intValue(); + String bedrockIdentifier = bedrockIdToIdentifier.get(bedrockId); + if (bedrockIdentifier == null) { + throw new RuntimeException("Missing Bedrock ID in mappings!: " + bedrockId); + } if (entry.getValue().has("tool_type")) { if (entry.getValue().has("tool_tier")) { ITEM_ENTRIES.put(itemIndex, new ToolItemEntry( - entry.getKey(), itemIndex, - entry.getValue().get("bedrock_id").intValue(), + entry.getKey(), bedrockIdentifier, itemIndex, bedrockId, entry.getValue().get("bedrock_data").intValue(), entry.getValue().get("tool_type").textValue(), entry.getValue().get("tool_tier").textValue(), entry.getValue().get("is_block") != null && entry.getValue().get("is_block").booleanValue())); } else { ITEM_ENTRIES.put(itemIndex, new ToolItemEntry( - entry.getKey(), itemIndex, - entry.getValue().get("bedrock_id").intValue(), + entry.getKey(), bedrockIdentifier, itemIndex, bedrockId, entry.getValue().get("bedrock_data").intValue(), entry.getValue().get("tool_type").textValue(), - "", - entry.getValue().get("is_block").booleanValue())); + "", entry.getValue().get("is_block").booleanValue())); } } else { ITEM_ENTRIES.put(itemIndex, new ItemEntry( - entry.getKey(), itemIndex, - entry.getValue().get("bedrock_id").intValue(), + entry.getKey(), bedrockIdentifier, itemIndex, bedrockId, entry.getValue().get("bedrock_data").intValue(), entry.getValue().get("is_block") != null && entry.getValue().get("is_block").booleanValue())); } @@ -197,7 +202,7 @@ public class ItemRegistry { } // Add the loadstone compass since it doesn't exist on java but we need it for item conversion - ITEM_ENTRIES.put(itemIndex, new ItemEntry("minecraft:lodestone_compass", itemIndex, + ITEM_ENTRIES.put(itemIndex, new ItemEntry("minecraft:lodestone_compass", "minecraft:lodestone_compass", itemIndex, lodestoneCompassId, 0, false)); /* Load creative items */ @@ -269,23 +274,6 @@ public class ItemRegistry { }); } - /** - * Finds the Bedrock string identifier of an ItemEntry - * - * @param entry the ItemEntry to search for - * @return the Bedrock identifier - */ - public static String getBedrockIdentifier(ItemEntry entry) { - String blockName = ""; - for (StartGamePacket.ItemEntry startGamePacketItemEntry : ItemRegistry.ITEMS) { - if (startGamePacketItemEntry.getId() == (short) entry.getBedrockId()) { - blockName = startGamePacketItemEntry.getIdentifier(); // Find the Bedrock string name - break; - } - } - return blockName; - } - /** * Gets a Bedrock {@link ItemData} from a {@link JsonNode} * @param itemNode the JSON node that contains ProxyPass-compatible Bedrock item data diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java index 00c9138a..0df179cf 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java @@ -34,16 +34,13 @@ import com.nukkitx.nbt.NbtType; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; -import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.ItemRemapper; +import org.geysermc.connector.network.translators.chat.MessageTranslator; import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.utils.FileUtils; import org.geysermc.connector.utils.LanguageUtils; -import org.geysermc.connector.network.translators.chat.MessageTranslator; import org.reflections.Reflections; import java.util.*; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ToolItemEntry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ToolItemEntry.java index 10edcdec..dde57700 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ToolItemEntry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ToolItemEntry.java @@ -32,8 +32,8 @@ public class ToolItemEntry extends ItemEntry { private final String toolType; private final String toolTier; - public ToolItemEntry(String javaIdentifier, int javaId, int bedrockId, int bedrockData, String toolType, String toolTier, boolean isBlock) { - super(javaIdentifier, javaId, bedrockId, bedrockData, isBlock); + public ToolItemEntry(String javaIdentifier, String bedrockIdentifier, int javaId, int bedrockId, int bedrockData, String toolType, String toolTier, boolean isBlock) { + super(javaIdentifier, bedrockIdentifier, javaId, bedrockId, bedrockData, isBlock); this.toolType = toolType; this.toolTier = toolTier; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/CompassTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/CompassTranslator.java index 159b9ab4..92ec67dd 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/CompassTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/CompassTranslator.java @@ -53,7 +53,7 @@ public class CompassTranslator extends ItemTranslator { Tag lodestoneTag = itemStack.getNbt().get("LodestoneTracked"); if (lodestoneTag instanceof ByteTag) { // Get the fake lodestonecompass entry - itemEntry = ItemRegistry.getItemEntry("minecraft:lodestonecompass"); + itemEntry = ItemRegistry.getItemEntry("minecraft:lodestone_compass"); // Get the loadstone pos CompoundTag loadstonePos = itemStack.getNbt().get("LodestonePos"); @@ -83,7 +83,7 @@ public class CompassTranslator extends ItemTranslator { @Override public ItemStack translateToJava(ItemData itemData, ItemEntry itemEntry) { boolean isLoadstone = false; - if (itemEntry.getJavaIdentifier().equals("minecraft:lodestonecompass")) { + if (itemEntry.getBedrockIdentifier().equals("minecraft:lodestone_compass")) { // Revert the entry back to the compass itemEntry = ItemRegistry.getItemEntry("minecraft:compass"); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/CrossbowTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/CrossbowTranslator.java index a05b9c8b..97da8669 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/CrossbowTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/CrossbowTranslator.java @@ -45,15 +45,15 @@ public class CrossbowTranslator extends NbtItemStackTranslator { if (!chargedProjectiles.getValue().isEmpty()) { CompoundTag projectile = (CompoundTag) chargedProjectiles.getValue().get(0); - ItemEntry entry = ItemRegistry.getItemEntry((String) projectile.get("id").getValue()); - if (entry == null) return; + ItemEntry projectileEntry = ItemRegistry.getItemEntry((String) projectile.get("id").getValue()); + if (projectileEntry == null) return; CompoundTag tag = projectile.get("tag"); ItemStack itemStack = new ItemStack(itemEntry.getJavaId(), (byte) projectile.get("Count").getValue(), tag); ItemData itemData = ItemTranslator.translateToBedrock(session, itemStack); CompoundTag newProjectile = new CompoundTag("chargedItem"); newProjectile.put(new ByteTag("Count", (byte) itemData.getCount())); - newProjectile.put(new StringTag("Name", ItemRegistry.getBedrockIdentifier(entry))); + newProjectile.put(new StringTag("Name", projectileEntry.getBedrockIdentifier())); newProjectile.put(new ShortTag("Damage", itemData.getDamage())); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java index 6ecb9a44..126d2e1f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java @@ -50,9 +50,8 @@ public class ShulkerBoxItemTranslator extends NbtItemStackTranslator { boxItemTag.put(new ByteTag("WasPickedUp", (byte) 0)); // ??? ItemEntry boxItemEntry = ItemRegistry.getItemEntry(((StringTag) itemData.get("id")).getValue()); - String blockName = ItemRegistry.getBedrockIdentifier(boxItemEntry); - boxItemTag.put(new StringTag("Name", blockName)); + boxItemTag.put(new StringTag("Name", boxItemEntry.getBedrockIdentifier())); boxItemTag.put(new ShortTag("Damage", (short) boxItemEntry.getBedrockData())); boxItemTag.put(new ByteTag("Count", ((ByteTag) itemData.get("Count")).getValue())); if (itemData.contains("tag")) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaTradeListTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaTradeListTranslator.java index e7e6df08..ad422a4c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaTradeListTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaTradeListTranslator.java @@ -139,7 +139,7 @@ public class JavaTradeListTranslator extends PacketTranslator Date: Wed, 18 Nov 2020 23:18:36 +0000 Subject: [PATCH 119/205] More chat fixes (#1557) * Fix positional translation arguments not being handled * Fix locale fallback * Fix command completion * Remove the expensive call to `containsKey` * Unify adventure versions * Fix some more formatting issues due to parity * Fix and update tests * Update adventure * Add Javadoc for getCommandNames * Formatting Co-authored-by: Camotoy <20743703+DoctorMacc@users.noreply.github.com> --- .../command/GeyserBungeeCommandExecutor.java | 2 +- .../command/GeyserSpigotCommandExecutor.java | 2 +- .../command/GeyserSpongeCommandExecutor.java | 2 +- .../GeyserVelocityCommandExecutor.java | 28 +++++++++++++------ connector/pom.xml | 8 +++--- .../connector/command/CommandManager.java | 11 ++++++-- .../translators/chat/MessageTranslator.java | 14 ++++++++-- .../chat/MinecraftTranslationRegistry.java | 10 +++++++ .../connector/utils/LanguageUtils.java | 6 +++- .../chat/MessageTranslatorTest.java | 20 +++++++++---- 10 files changed, 76 insertions(+), 27 deletions(-) diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/GeyserBungeeCommandExecutor.java b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/GeyserBungeeCommandExecutor.java index ff7a2e3d..c25da086 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/GeyserBungeeCommandExecutor.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/GeyserBungeeCommandExecutor.java @@ -67,7 +67,7 @@ public class GeyserBungeeCommandExecutor extends Command implements TabExecutor @Override public Iterable onTabComplete(CommandSender sender, String[] args) { if (args.length == 1) { - return Arrays.asList("?", "help", "reload", "shutdown", "stop"); + return connector.getCommandManager().getCommandNames(); } return new ArrayList<>(); } diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/GeyserSpigotCommandExecutor.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/GeyserSpigotCommandExecutor.java index 0edb8448..1ff3e7fe 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/GeyserSpigotCommandExecutor.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/GeyserSpigotCommandExecutor.java @@ -67,7 +67,7 @@ public class GeyserSpigotCommandExecutor implements TabExecutor { @Override public List onTabComplete(CommandSender sender, Command command, String label, String[] args) { if (args.length == 1) { - return Arrays.asList("?", "help", "reload", "shutdown", "stop"); + return connector.getCommandManager().getCommandNames(); } return new ArrayList<>(); } diff --git a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/GeyserSpongeCommandExecutor.java b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/GeyserSpongeCommandExecutor.java index c77e8271..3171a1a9 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/GeyserSpongeCommandExecutor.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/GeyserSpongeCommandExecutor.java @@ -70,7 +70,7 @@ public class GeyserSpongeCommandExecutor implements CommandCallable { @Override public List getSuggestions(CommandSource source, String arguments, @Nullable Location targetPosition) throws CommandException { if (arguments.split(" ").length == 1) { - return Arrays.asList("?", "help", "reload", "shutdown", "stop"); + return connector.getCommandManager().getCommandNames(); } return new ArrayList<>(); } diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandExecutor.java b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandExecutor.java index c329fb19..30c47413 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandExecutor.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandExecutor.java @@ -25,8 +25,8 @@ package org.geysermc.platform.velocity.command; -import com.velocitypowered.api.command.Command; import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.command.SimpleCommand; import lombok.AllArgsConstructor; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.CommandSender; @@ -34,29 +34,39 @@ import org.geysermc.connector.command.GeyserCommand; import org.geysermc.connector.common.ChatColor; import org.geysermc.connector.utils.LanguageUtils; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; @AllArgsConstructor -public class GeyserVelocityCommandExecutor implements Command { +public class GeyserVelocityCommandExecutor implements SimpleCommand { private final GeyserConnector connector; @Override - public void execute(CommandSource source, String[] args) { - if (args.length > 0) { - if (getCommand(args[0]) != null) { - if (!source.hasPermission(getCommand(args[0]).getPermission())) { - CommandSender sender = new VelocityCommandSender(source); + public void execute(Invocation invocation) { + if (invocation.arguments().length > 0) { + if (getCommand(invocation.arguments()[0]) != null) { + if (!invocation.source().hasPermission(getCommand(invocation.arguments()[0]).getPermission())) { + CommandSender sender = new VelocityCommandSender(invocation.source()); sender.sendMessage(ChatColor.RED + LanguageUtils.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.getLocale())); return; } - getCommand(args[0]).execute(new VelocityCommandSender(source), args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]); + getCommand(invocation.arguments()[0]).execute(new VelocityCommandSender(invocation.source()), invocation.arguments().length > 1 ? Arrays.copyOfRange(invocation.arguments(), 1, invocation.arguments().length) : new String[0]); } } else { - getCommand("help").execute(new VelocityCommandSender(source), new String[0]); + getCommand("help").execute(new VelocityCommandSender(invocation.source()), new String[0]); } } + @Override + public List suggest(Invocation invocation) { + if (invocation.arguments().length == 0) { + return connector.getCommandManager().getCommandNames(); + } + return new ArrayList<>(); + } + private GeyserCommand getCommand(String label) { return connector.getCommandManager().getCommands().get(label); } diff --git a/connector/pom.xml b/connector/pom.xml index 7c44ddfd..95794ff2 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -136,21 +136,21 @@ 2.1.3 - net.kyori + com.github.kyoripowered.adventure adventure-api - 4.1.1 + 7acd956 compile com.github.kyoripowered.adventure adventure-text-serializer-gson - 4d8a67d798 + 7acd956 compile com.github.kyoripowered.adventure adventure-text-serializer-legacy - 0599048 + 7acd956 compile diff --git a/connector/src/main/java/org/geysermc/connector/command/CommandManager.java b/connector/src/main/java/org/geysermc/connector/command/CommandManager.java index 7adce430..822b6dea 100644 --- a/connector/src/main/java/org/geysermc/connector/command/CommandManager.java +++ b/connector/src/main/java/org/geysermc/connector/command/CommandManager.java @@ -31,9 +31,7 @@ import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.defaults.*; import org.geysermc.connector.utils.LanguageUtils; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; +import java.util.*; public abstract class CommandManager { @@ -92,6 +90,13 @@ public abstract class CommandManager { cmd.execute(sender, args); } + /** + * @return a list of all subcommands under {@code /geyser}. + */ + public List getCommandNames() { + return Arrays.asList(connector.getCommandManager().getCommands().keySet().toArray(new String[0])); + } + /** * Returns the description of the given command * diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/chat/MessageTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/chat/MessageTranslator.java index be01362f..b7e6838e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/chat/MessageTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/chat/MessageTranslator.java @@ -84,7 +84,17 @@ public class MessageTranslator { Locale localeCode = Locale.forLanguageTag(locale.replace('_', '-')); component = RENDERER.render(component, localeCode); - return LegacyComponentSerializer.legacySection().serialize(component); + String legacy = LegacyComponentSerializer.legacySection().serialize(component); + + // Strip strikethrough and underline as they are not supported on bedrock + legacy = legacy.replaceAll("\u00a7[mn]", ""); + + // Make color codes reset formatting like Java + // See https://minecraft.gamepedia.com/Formatting_codes#Usage + legacy = legacy.replaceAll("\u00a7([0-9a-f])", "\u00a7r\u00a7$1"); + legacy = legacy.replaceAll("\u00a7r\u00a7r", "\u00a7r"); + + return legacy; } public static String convertMessage(String message) { @@ -106,7 +116,7 @@ public class MessageTranslator { String convertedMessage = convertMessage(convertToJavaMessage(message), locale); // We have to do this since Adventure strips the starting reset character - if (message.startsWith(getColor(ChatColor.RESET))) { + if (message.startsWith(getColor(ChatColor.RESET)) && !convertedMessage.startsWith(getColor(ChatColor.RESET))) { convertedMessage = getColor(ChatColor.RESET) + convertedMessage; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/chat/MinecraftTranslationRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/chat/MinecraftTranslationRegistry.java index a23167ac..127e0060 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/chat/MinecraftTranslationRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/chat/MinecraftTranslationRegistry.java @@ -61,6 +61,16 @@ public class MinecraftTranslationRegistry implements TranslationRegistry { } m.appendTail(sb); + // Replace the `%x$s` with numbered inserts `{x}` + p = Pattern.compile("%([0-9]+)\\$s"); + m = p.matcher(sb.toString()); + sb = new StringBuffer(); + while (m.find()) { + i = Integer.parseInt(m.group(1)) - 1; + m.appendReplacement(sb, "{" + i + "}"); + } + m.appendTail(sb); + return new MessageFormat(sb.toString(), locale); } diff --git a/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java index 06d2936e..87722e5b 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java @@ -105,7 +105,11 @@ public class LanguageUtils { locale = formatLocale(locale); Properties properties = LOCALE_MAPPINGS.get(locale); - String formatString = properties.getProperty(key); + String formatString = null; + + if (properties != null) { + formatString = properties.getProperty(key); + } // Try and get the key from the default locale if (formatString == null) { diff --git a/connector/src/test/java/org/geysermc/connector/network/translators/chat/MessageTranslatorTest.java b/connector/src/test/java/org/geysermc/connector/network/translators/chat/MessageTranslatorTest.java index 5d52c79b..4dd6a04c 100644 --- a/connector/src/test/java/org/geysermc/connector/network/translators/chat/MessageTranslatorTest.java +++ b/connector/src/test/java/org/geysermc/connector/network/translators/chat/MessageTranslatorTest.java @@ -39,22 +39,31 @@ public class MessageTranslatorTest { @Before public void setUp() throws Exception { messages.put("{\"text\":\"\",\"extra\":[{\"text\":\"DoctorMad9952 joined the game\",\"color\":\"yellow\"}]}", - "§eDoctorMad9952 joined the game"); + "§r§eDoctorMad9952 joined the game"); messages.put("{\"text\":\"\",\"extra\":[\"Plugins (3): \",{\"text\":\"WorldEdit\",\"color\":\"green\"},{\"text\":\", \",\"color\":\"white\"},{\"text\":\"ViaVersion\",\"color\":\"green\"},{\"text\":\", \",\"color\":\"white\"},{\"text\":\"Geyser-Spigot\",\"color\":\"green\"}]}", - "Plugins (3): §aWorldEdit§f, §aViaVersion§f, §aGeyser-Spigot"); + "Plugins (3): §r§aWorldEdit§r§f, §r§aViaVersion§r§f, §r§aGeyser-Spigot"); // RGB downgrade test messages.put("{\"extra\":[{\"text\":\" \"},{\"color\":\"gold\",\"text\":\"The \"},{\"color\":\"#E14248\",\"obfuscated\":true,\"text\":\"||\"},{\"color\":\"#3AA9FF\",\"bold\":true,\"text\":\"CubeCraft\"},{\"color\":\"#E14248\",\"obfuscated\":true,\"text\":\"||\"},{\"color\":\"gold\",\"text\":\" Network \"},{\"color\":\"green\",\"text\":\"[1.8/1.9+]\\n \"},{\"color\":\"#f5e342\",\"text\":\"✦ \"},{\"color\":\"#b042f5\",\"bold\":true,\"text\":\"N\"},{\"color\":\"#c142f5\",\"bold\":true,\"text\":\"E\"},{\"color\":\"#d342f5\",\"bold\":true,\"text\":\"W\"},{\"color\":\"#e442f5\",\"bold\":true,\"text\":\":\"},{\"color\":\"#f542f5\",\"bold\":true,\"text\":\" \"},{\"color\":\"#bcf542\",\"bold\":true,\"text\":\"A\"},{\"color\":\"#acee3f\",\"bold\":true,\"text\":\"M\"},{\"color\":\"#9ce73c\",\"bold\":true,\"text\":\"O\"},{\"color\":\"#8ce039\",\"bold\":true,\"text\":\"N\"},{\"color\":\"#7cd936\",\"bold\":true,\"text\":\"G\"},{\"color\":\"#6cd233\",\"bold\":true,\"text\":\" \"},{\"color\":\"#5ccb30\",\"bold\":true,\"text\":\"S\"},{\"color\":\"#4cc42d\",\"bold\":true,\"text\":\"L\"},{\"color\":\"#3cbd2a\",\"bold\":true,\"text\":\"I\"},{\"color\":\"#2cb627\",\"bold\":true,\"text\":\"M\"},{\"color\":\"#1caf24\",\"bold\":true,\"text\":\"E\"},{\"color\":\"#0ca821\",\"bold\":true,\"text\":\"S\"},{\"color\":\"#f5e342\",\"text\":\" \"},{\"color\":\"#6d7c87\",\"text\":\"(kinda sus) \"},{\"color\":\"#f5e342\",\"text\":\"✦\"}],\"text\":\"\"}", - " §6The §c§k||§r§3§lCubeCraft§r§c§k||§r§6 Network §a[1.8/1.9+]\n" + - " §e✦ §d§lN§r§d§lE§r§d§lW§r§d§l:§r§d§l §r§e§lA§r§e§lM§r§a§lO§r§a§lN§r§a§lG§r§a§l §r§a§lS§r§a§lL§r§2§lI§r§2§lM§r§2§lE§r§2§lS§r§e §8(kinda sus) §e✦"); + " §r§6The §r§c§k||§r§3§lCubeCraft§r§c§k||§r§6 Network §r§a[1.8/1.9+]\n" + + " §r§e✦ §r§d§lN§r§d§lE§r§d§lW§r§d§l:§r§d§l §r§e§lA§r§e§lM§r§a§lO§r§a§lN§r§a§lG§r§a§l §r§a§lS§r§a§lL§r§2§lI§r§2§lM§r§2§lE§r§2§lS§r§e §r§8(kinda sus) §r§e✦"); + + // Color code format resetting + messages.put("{\"text\":\"\",\"extra\":[{\"text\":\"\",\"extra\":[{\"text\":\"[\",\"color\":\"gray\"},{\"text\":\"H\",\"color\":\"yellow\"},{\"text\":\"]\",\"color\":\"gray\"},{\"text\":\" \",\"color\":\"white\"},{\"text\":\"GUEST\",\"color\":\"#b7b7b7\",\"bold\":true}]},{\"text\":\"\",\"extra\":[{\"text\":\" \",\"bold\":true},{\"text\":\"»\",\"color\":\"blue\"},{\"text\":\" \",\"color\":\"gray\"}]},{\"text\":\"\",\"extra\":[{\"text\":\"rtm516\",\"color\":\"white\"},{\"text\":\": \",\"color\":\"gray\"},{\"text\":\"\",\"color\":\"white\"}]},{\"text\":\"\",\"extra\":[{\"text\":\"This is an amazing bedrock test message\",\"color\":\"white\"}]}]}\n", + "§r§7[§r§eH§r§7]§r§f §r§7§lGUEST§r§l §r§9»§r§7 §r§frtm516§r§7: §r§fThis is an amazing bedrock test message"); + + // Test translation and positional arguments + // Disabled due to not having an GeyserConnector instance, hence it fails + //messages.put("{\"translate\":\"death.attack.player\",\"with\":[{\"text\":\"rtm516\",\"insertion\":\"rtm516\"},{\"text\":\"*invincible_rt\",\"insertion\":\"*invincible_rt\"}]}", + // "rtm516 was slain by *invincible_rt"); } @Test public void convertMessage() { for (Map.Entry entry : messages.entrySet()) { String bedrockMessage = MessageTranslator.convertMessage(entry.getKey(), "en_US"); - Assert.assertEquals("Translation of messages is incorrect", bedrockMessage, entry.getValue()); + Assert.assertEquals("Translation of messages is incorrect", entry.getValue(), bedrockMessage); } } @@ -63,5 +72,6 @@ public class MessageTranslatorTest { Assert.assertEquals("All newline message is not handled properly", "\n\n\n\n", MessageTranslator.convertMessageLenient("\n\n\n\n")); Assert.assertEquals("Empty message is not handled properly", "", MessageTranslator.convertMessageLenient("")); Assert.assertEquals("Reset before message is not handled properly", "§r§eGame Selector", MessageTranslator.convertMessageLenient("§r§eGame Selector")); + Assert.assertEquals("Unimplemented formatting chars not stripped", "Bold Underline", MessageTranslator.convertMessageLenient("§m§nBold Underline")); } } From a70d3e2150e8b50c3fd2b74710d6e8fa504a6d07 Mon Sep 17 00:00:00 2001 From: circuit10 Date: Fri, 20 Nov 2020 19:56:39 +0000 Subject: [PATCH 120/205] Fix inconsistencies with movement and position (#699) Movement is now significant better, especially on slabs, stairs, and other half-blocks. Co-authored-by: RednedEpic Co-authored-by: DoctorMacc Co-authored-by: Tim203 Co-authored-by: Camotoy <20743703+DoctorMacc@users.noreply.github.com> --- bootstrap/spigot/pom.xml | 5 + .../platform/spigot/GeyserSpigotPlugin.java | 99 ++++++++- .../world/GeyserSpigotBlockPlaceListener.java | 15 +- .../GeyserSpigot1_12NativeWorldManager.java | 59 +++++ .../manager/GeyserSpigot1_12WorldManager.java | 141 ++++++++++++ .../GeyserSpigotFallbackWorldManager.java | 62 ++++++ .../GeyserSpigotLegacyNativeWorldManager.java | 79 +++++++ .../GeyserSpigotNativeWorldManager.java | 51 +++++ .../GeyserSpigotWorldManager.java | 118 +++------- connector/pom.xml | 2 + .../geysermc/connector/GeyserConnector.java | 2 + .../configuration/GeyserConfiguration.java | 2 + .../GeyserJacksonConfiguration.java | 3 + .../org/geysermc/connector/entity/Entity.java | 3 +- .../connector/entity/FireworkEntity.java | 1 + .../entity/{ => player}/PlayerEntity.java | 27 ++- .../entity/player/SessionPlayerEntity.java | 67 ++++++ .../connector/entity/type/EntityType.java | 1 + .../network/session/GeyserSession.java | 112 ++++++++-- .../network/session/cache/EntityCache.java | 2 +- .../network/session/cache/TeleportCache.java | 42 +++- .../bedrock/BedrockRespawnTranslator.java | 2 +- ...SetLocalPlayerAsInitializedTranslator.java | 2 +- .../player/BedrockActionTranslator.java | 1 + .../player/BedrockMovePlayerTranslator.java | 163 +++++++++----- .../translators/collision/BoundingBox.java | 52 +++++ .../collision/CollisionManager.java | 208 ++++++++++++++++++ .../collision/CollisionRemapper.java | 57 +++++ .../collision/CollisionTranslator.java | 192 ++++++++++++++++ .../collision/translators/BlockCollision.java | 154 +++++++++++++ .../collision/translators/DoorCollision.java | 92 ++++++++ .../collision/translators/EmptyCollision.java | 35 +++ .../translators/GrassPathCollision.java | 49 +++++ .../collision/translators/OtherCollision.java | 53 +++++ .../translators/ScaffoldingCollision.java | 72 ++++++ .../collision/translators/SnowCollision.java | 103 +++++++++ .../collision/translators/SolidCollision.java | 39 ++++ .../translators/TrapdoorCollision.java | 105 +++++++++ .../java/JavaJoinGameTranslator.java | 2 +- .../entity/JavaEntityEffectTranslator.java | 8 +- .../JavaEntityRemoveEffectTranslator.java | 8 +- .../player/JavaPlayerAbilitiesTranslator.java | 10 +- .../player/JavaPlayerListEntryTranslator.java | 2 +- .../JavaPlayerPositionRotationTranslator.java | 16 +- .../spawn/JavaSpawnPlayerTranslator.java | 2 +- .../world/JavaNotifyClientTranslator.java | 2 +- .../world/block/BlockTranslator.java | 8 + .../geysermc/connector/utils/BlockUtils.java | 4 +- .../connector/utils/DimensionUtils.java | 4 +- .../geysermc/connector/utils/SkinUtils.java | 2 +- connector/src/main/resources/config.yml | 21 +- connector/src/main/resources/languages | 2 +- connector/src/main/resources/mappings | 2 +- 53 files changed, 2117 insertions(+), 248 deletions(-) create mode 100644 bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12NativeWorldManager.java create mode 100644 bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12WorldManager.java create mode 100644 bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotFallbackWorldManager.java create mode 100644 bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotLegacyNativeWorldManager.java create mode 100644 bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotNativeWorldManager.java rename bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/{ => manager}/GeyserSpigotWorldManager.java (59%) rename connector/src/main/java/org/geysermc/connector/entity/{ => player}/PlayerEntity.java (94%) create mode 100644 connector/src/main/java/org/geysermc/connector/entity/player/SessionPlayerEntity.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/collision/BoundingBox.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/collision/CollisionManager.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/collision/CollisionRemapper.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/collision/CollisionTranslator.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/BlockCollision.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/DoorCollision.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/EmptyCollision.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/GrassPathCollision.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/OtherCollision.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/ScaffoldingCollision.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/SnowCollision.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/SolidCollision.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/TrapdoorCollision.java diff --git a/bootstrap/spigot/pom.xml b/bootstrap/spigot/pom.xml index f3b9cf88..adaa7557 100644 --- a/bootstrap/spigot/pom.xml +++ b/bootstrap/spigot/pom.xml @@ -29,6 +29,11 @@ 3.2.0 provided + + org.geysermc.adapters + spigot-all + 1.0-SNAPSHOT + ${outputName}-Spigot diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java index 741763ec..39d4f993 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java @@ -25,8 +25,10 @@ package org.geysermc.platform.spigot; +import com.github.steveice10.mc.protocol.MinecraftConstants; import org.bukkit.Bukkit; import org.bukkit.plugin.java.JavaPlugin; +import org.geysermc.adapters.spigot.SpigotAdapters; import org.geysermc.common.PlatformType; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.bootstrap.GeyserBootstrap; @@ -42,18 +44,23 @@ import org.geysermc.platform.spigot.command.GeyserSpigotCommandExecutor; import org.geysermc.platform.spigot.command.GeyserSpigotCommandManager; import org.geysermc.platform.spigot.command.SpigotCommandSender; import org.geysermc.platform.spigot.world.GeyserSpigotBlockPlaceListener; -import org.geysermc.platform.spigot.world.GeyserSpigotWorldManager; +import org.geysermc.platform.spigot.world.manager.*; +import us.myles.ViaVersion.api.Pair; import us.myles.ViaVersion.api.Via; +import us.myles.ViaVersion.api.data.MappingData; +import us.myles.ViaVersion.api.protocol.Protocol; +import us.myles.ViaVersion.api.protocol.ProtocolRegistry; +import us.myles.ViaVersion.api.protocol.ProtocolVersion; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.List; import java.util.UUID; import java.util.logging.Level; public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { - private GeyserSpigotCommandManager geyserCommandManager; private GeyserSpigotConfiguration geyserConfig; private GeyserSpigotLogger geyserLogger; @@ -142,8 +149,48 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { // Set if we need to use a different method for getting a player's locale SpigotCommandSender.setUseLegacyLocaleMethod(!isCompatible(Bukkit.getServer().getVersion(), "1.12.0")); - this.geyserWorldManager = new GeyserSpigotWorldManager(isLegacy, use3dBiomes, isViaVersion); - GeyserSpigotBlockPlaceListener blockPlaceListener = new GeyserSpigotBlockPlaceListener(connector, isLegacy, isViaVersion); + if (connector.getConfig().isUseAdapters()) { + try { + String name = Bukkit.getServer().getClass().getPackage().getName(); + String nmsVersion = name.substring(name.lastIndexOf('.') + 1); + SpigotAdapters.registerWorldAdapter(nmsVersion); + if (isViaVersion && isViaVersionNeeded()) { + if (isLegacy) { + // Pre-1.13 + this.geyserWorldManager = new GeyserSpigot1_12NativeWorldManager(); + } else { + // Post-1.13 + this.geyserWorldManager = new GeyserSpigotLegacyNativeWorldManager(this, use3dBiomes); + } + } else { + // No ViaVersion + this.geyserWorldManager = new GeyserSpigotNativeWorldManager(use3dBiomes); + } + geyserLogger.debug("Using NMS adapter: " + this.geyserWorldManager.getClass() + ", " + nmsVersion); + } catch (Exception e) { + if (geyserConfig.isDebugMode()) { + geyserLogger.debug("Error while attempting to find NMS adapter. Most likely, this can be safely ignored. :)"); + e.printStackTrace(); + } + } + } else { + geyserLogger.debug("Not using NMS adapter as it is disabled in the config."); + } + if (this.geyserWorldManager == null) { + // No NMS adapter + if (isLegacy && isViaVersion) { + // Use ViaVersion for converting pre-1.13 block states + this.geyserWorldManager = new GeyserSpigot1_12WorldManager(); + } else if (isLegacy) { + // Not sure how this happens - without ViaVersion, we don't know any block states, so just assume everything is air + this.geyserWorldManager = new GeyserSpigotFallbackWorldManager(); + } else { + // Post-1.13 + this.geyserWorldManager = new GeyserSpigotWorldManager(use3dBiomes); + } + geyserLogger.debug("Using default world manager: " + this.geyserWorldManager.getClass()); + } + GeyserSpigotBlockPlaceListener blockPlaceListener = new GeyserSpigotBlockPlaceListener(connector, this.geyserWorldManager); Bukkit.getServer().getPluginManager().registerEvents(blockPlaceListener, this); @@ -152,8 +199,9 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { @Override public void onDisable() { - if (connector != null) + if (connector != null) { connector.shutdown(); + } } @Override @@ -186,6 +234,11 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { return getDataFolder().toPath(); } + @Override + public BootstrapDumpInfo getDumpInfo() { + return new GeyserSpigotDumpInfo(); + } + public boolean isCompatible(String version, String whichVersion) { int[] currentVersion = parseVersion(version); int[] otherVersion = parseVersion(whichVersion); @@ -213,15 +266,43 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { String t = stringArray[index].replaceAll("\\D", ""); try { temp[index] = Integer.parseInt(t); - } catch(NumberFormatException ex) { + } catch (NumberFormatException ex) { temp[index] = 0; } } return temp; } - @Override - public BootstrapDumpInfo getDumpInfo() { - return new GeyserSpigotDumpInfo(); + /** + * @return the server version before ViaVersion finishes initializing + */ + public ProtocolVersion getServerProtocolVersion() { + String bukkitVersion = Bukkit.getServer().getVersion(); + // Turn "(MC: 1.16.4)" into 1.16.4. + String version = bukkitVersion.split("\\(MC: ")[1].split("\\)")[0]; + return ProtocolVersion.getClosest(version); + } + + /** + * This function should not run unless ViaVersion is installed on the server. + * + * @return true if there is any block mappings difference between the server and client. + */ + private boolean isViaVersionNeeded() { + ProtocolVersion serverVersion = getServerProtocolVersion(); + List> protocolList = ProtocolRegistry.getProtocolPath(MinecraftConstants.PROTOCOL_VERSION, + serverVersion.getVersion()); + if (protocolList == null) { + // No translation needed! + return false; + } + for (int i = protocolList.size() - 1; i >= 0; i--) { + MappingData mappingData = protocolList.get(i).getValue().getMappingData(); + if (mappingData != null) { + return true; + } + } + // All mapping data is null, which means client and server block states are the same + return false; } } diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotBlockPlaceListener.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotBlockPlaceListener.java index cb59e202..55a368be 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotBlockPlaceListener.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotBlockPlaceListener.java @@ -36,13 +36,13 @@ import org.bukkit.event.block.BlockPlaceEvent; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.block.BlockTranslator; +import org.geysermc.platform.spigot.world.manager.GeyserSpigotWorldManager; @AllArgsConstructor public class GeyserSpigotBlockPlaceListener implements Listener { private final GeyserConnector connector; - private final boolean isLegacy; - private final boolean isViaVersion; + private final GeyserSpigotWorldManager worldManager; @EventHandler public void place(final BlockPlaceEvent event) { @@ -52,14 +52,13 @@ public class GeyserSpigotBlockPlaceListener implements Listener { placeBlockSoundPacket.setSound(SoundEvent.PLACE); placeBlockSoundPacket.setPosition(Vector3f.from(event.getBlockPlaced().getX(), event.getBlockPlaced().getY(), event.getBlockPlaced().getZ())); placeBlockSoundPacket.setBabySound(false); - String javaBlockId; - if (isLegacy) { - javaBlockId = BlockTranslator.getJavaIdBlockMap().inverse().get(GeyserSpigotWorldManager.getLegacyBlock(session, - event.getBlockPlaced().getX(), event.getBlockPlaced().getY(), event.getBlockPlaced().getZ(), isViaVersion)); + if (worldManager.isLegacy()) { + placeBlockSoundPacket.setExtraData(BlockTranslator.getBedrockBlockId(worldManager.getBlockAt(session, + event.getBlockPlaced().getX(), event.getBlockPlaced().getY(), event.getBlockPlaced().getZ()))); } else { - javaBlockId = event.getBlockPlaced().getBlockData().getAsString(); + String javaBlockId = event.getBlockPlaced().getBlockData().getAsString(); + placeBlockSoundPacket.setExtraData(BlockTranslator.getBedrockBlockId(BlockTranslator.getJavaIdBlockMap().getOrDefault(javaBlockId, BlockTranslator.JAVA_AIR_ID))); } - placeBlockSoundPacket.setExtraData(BlockTranslator.getBedrockBlockId(BlockTranslator.getJavaIdBlockMap().getOrDefault(javaBlockId, 0))); placeBlockSoundPacket.setIdentifier(":"); session.sendUpstreamPacket(placeBlockSoundPacket); session.setLastBlockPlacePosition(null); diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12NativeWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12NativeWorldManager.java new file mode 100644 index 00000000..f58b75cd --- /dev/null +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12NativeWorldManager.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.platform.spigot.world.manager; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.geysermc.adapters.spigot.SpigotAdapters; +import org.geysermc.adapters.spigot.SpigotWorldAdapter; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; +import us.myles.ViaVersion.api.Via; +import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.storage.BlockStorage; + +/** + * Used with ViaVersion and pre-1.13. + */ +public class GeyserSpigot1_12NativeWorldManager extends GeyserSpigot1_12WorldManager { + private final SpigotWorldAdapter adapter; + + public GeyserSpigot1_12NativeWorldManager() { + this.adapter = SpigotAdapters.getWorldAdapter(); + // Unlike post-1.13, we can't build up a cache of block states, because block entities need some special conversion + } + + @Override + public int getBlockAt(GeyserSession session, int x, int y, int z) { + Player player = Bukkit.getPlayer(session.getPlayerEntity().getUsername()); + if (player == null) { + return BlockTranslator.JAVA_AIR_ID; + } + // Get block entity storage + BlockStorage storage = Via.getManager().getConnection(player.getUniqueId()).get(BlockStorage.class); + int blockId = adapter.getBlockAt(player.getWorld(), x, y, z); + return getLegacyBlock(storage, blockId, x, y, z); + } +} diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12WorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12WorldManager.java new file mode 100644 index 00000000..b00ddafa --- /dev/null +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12WorldManager.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.platform.spigot.world.manager; + +import com.github.steveice10.mc.protocol.data.game.chunk.Chunk; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; +import us.myles.ViaVersion.api.Pair; +import us.myles.ViaVersion.api.Via; +import us.myles.ViaVersion.api.data.MappingData; +import us.myles.ViaVersion.api.minecraft.Position; +import us.myles.ViaVersion.api.protocol.Protocol; +import us.myles.ViaVersion.api.protocol.ProtocolRegistry; +import us.myles.ViaVersion.api.protocol.ProtocolVersion; +import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.Protocol1_13To1_12_2; +import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.storage.BlockStorage; + +import java.util.List; + +/** + * Should be used when ViaVersion is present, no NMS adapter is being used, and we are pre-1.13. + * + * You need ViaVersion to connect to an older server with the Geyser-Spigot plugin. + */ +public class GeyserSpigot1_12WorldManager extends GeyserSpigotWorldManager { + /** + * Specific mapping data for 1.12 to 1.13. Used to convert the 1.12 block into the 1.13 block state. + * (Block IDs did not change between server versions until 1.13 and after) + */ + private final MappingData mappingData1_12to1_13; + + /** + * The list of all protocols from the client's version to 1.13. + */ + private final List> protocolList; + + public GeyserSpigot1_12WorldManager() { + super(false); + this.mappingData1_12to1_13 = ProtocolRegistry.getProtocol(Protocol1_13To1_12_2.class).getMappingData(); + this.protocolList = ProtocolRegistry.getProtocolPath(CLIENT_PROTOCOL_VERSION, + ProtocolVersion.v1_13.getVersion()); + } + + @Override + @SuppressWarnings("deprecation") + public int getBlockAt(GeyserSession session, int x, int y, int z) { + Player player = Bukkit.getPlayer(session.getPlayerEntity().getUsername()); + if (player == null) { + return BlockTranslator.JAVA_AIR_ID; + } + // Get block entity storage + BlockStorage storage = Via.getManager().getConnection(player.getUniqueId()).get(BlockStorage.class); + Block block = player.getWorld().getBlockAt(x, y, z); + // Black magic that gets the old block state ID + int blockId = (block.getType().getId() << 4) | (block.getData() & 0xF); + return getLegacyBlock(storage, blockId, x, y, z); + } + + /** + * + * @param storage ViaVersion's block entity storage (used to fix block entity state differences) + * @param blockId the pre-1.13 block id + * @param x X coordinate of block + * @param y Y coordinate of block + * @param z Z coordinate of block + * @return the block state updated to the latest Minecraft version + */ + @SuppressWarnings("deprecation") + public int getLegacyBlock(BlockStorage storage, int blockId, int x, int y, int z) { + // Convert block state from old version (1.12.2) -> 1.13 -> 1.13.1 -> 1.14 -> 1.15 -> 1.16 -> 1.16.2 + blockId = mappingData1_12to1_13.getNewBlockId(blockId); + // Translate block entity differences - some information was stored in block tags and not block states + if (storage.isWelcome(blockId)) { // No getOrDefault method + BlockStorage.ReplacementData data = storage.get(new Position(x, (short) y, z)); + if (data != null && data.getReplacement() != -1) { + blockId = data.getReplacement(); + } + } + for (int i = protocolList.size() - 1; i >= 0; i--) { + MappingData mappingData = protocolList.get(i).getValue().getMappingData(); + if (mappingData != null) { + blockId = mappingData.getNewBlockStateId(blockId); + } + } + return blockId; + } + + @SuppressWarnings("deprecation") + @Override + public void getBlocksInSection(GeyserSession session, int x, int y, int z, Chunk chunk) { + Player player = Bukkit.getPlayer(session.getPlayerEntity().getUsername()); + if (player == null) { + return; + } + World world = player.getWorld(); + // Get block entity storage + BlockStorage storage = Via.getManager().getConnection(player.getUniqueId()).get(BlockStorage.class); + for (int blockY = 0; blockY < 16; blockY++) { // Cache-friendly iteration order + for (int blockZ = 0; blockZ < 16; blockZ++) { + for (int blockX = 0; blockX < 16; blockX++) { + Block block = world.getBlockAt(x, y, z); + // Black magic that gets the old block state ID + int blockId = (block.getType().getId() << 4) | (block.getData() & 0xF); + chunk.set(blockX, blockY, blockZ, getLegacyBlock(storage, blockId, (x << 4) + blockX, (y << 4) + blockY, (z << 4) + blockZ)); + } + } + } + } + + @Override + public boolean isLegacy() { + return true; + } +} diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotFallbackWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotFallbackWorldManager.java new file mode 100644 index 00000000..49c675a1 --- /dev/null +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotFallbackWorldManager.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.platform.spigot.world.manager; + +import com.github.steveice10.mc.protocol.data.game.chunk.Chunk; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; + +/** + * Should only be used when we know {@link GeyserSpigotWorldManager#getBlockAt(GeyserSession, int, int, int)} + * cannot be accurate. Typically, this is when ViaVersion is not installed but a client still manages to connect. + * If this occurs to you somehow, please let us know!! + */ +public class GeyserSpigotFallbackWorldManager extends GeyserSpigotWorldManager { + public GeyserSpigotFallbackWorldManager() { + // Since this is pre-1.13 (and thus pre-1.15), there will never be 3D biomes. + super(false); + } + + @Override + public int getBlockAt(GeyserSession session, int x, int y, int z) { + return BlockTranslator.JAVA_AIR_ID; + } + + @Override + public void getBlocksInSection(GeyserSession session, int x, int y, int z, Chunk chunk) { + // Do nothing, since we can't do anything with the chunk + } + + @Override + public boolean hasMoreBlockDataThanChunkCache() { + return false; + } + + @Override + public boolean isLegacy() { + return true; + } +} diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotLegacyNativeWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotLegacyNativeWorldManager.java new file mode 100644 index 00000000..dec9b414 --- /dev/null +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotLegacyNativeWorldManager.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.platform.spigot.world.manager; + +import com.github.steveice10.mc.protocol.MinecraftConstants; +import it.unimi.dsi.fastutil.ints.Int2IntMap; +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntList; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.platform.spigot.GeyserSpigotPlugin; +import us.myles.ViaVersion.api.Pair; +import us.myles.ViaVersion.api.data.MappingData; +import us.myles.ViaVersion.api.protocol.Protocol; +import us.myles.ViaVersion.api.protocol.ProtocolRegistry; +import us.myles.ViaVersion.api.protocol.ProtocolVersion; + +import java.util.List; + +/** + * Used when block IDs need to be translated to the latest version + */ +public class GeyserSpigotLegacyNativeWorldManager extends GeyserSpigotNativeWorldManager { + + private final Int2IntMap oldToNewBlockId; + + public GeyserSpigotLegacyNativeWorldManager(GeyserSpigotPlugin plugin, boolean use3dBiomes) { + super(use3dBiomes); + IntList allBlockStates = adapter.getAllBlockStates(); + oldToNewBlockId = new Int2IntOpenHashMap(allBlockStates.size()); + ProtocolVersion serverVersion = plugin.getServerProtocolVersion(); + List> protocolList = ProtocolRegistry.getProtocolPath(MinecraftConstants.PROTOCOL_VERSION, + serverVersion.getVersion()); + for (int oldBlockId : allBlockStates) { + int newBlockId = oldBlockId; + // protocolList should *not* be null; we checked for that before initializing this class + for (int i = protocolList.size() - 1; i >= 0; i--) { + MappingData mappingData = protocolList.get(i).getValue().getMappingData(); + if (mappingData != null) { + newBlockId = mappingData.getNewBlockStateId(newBlockId); + } + } + oldToNewBlockId.put(oldBlockId, newBlockId); + } + } + + @Override + public int getBlockAt(GeyserSession session, int x, int y, int z) { + int nativeBlockId = super.getBlockAt(session, x, y, z); + return oldToNewBlockId.getOrDefault(nativeBlockId, nativeBlockId); + } + + @Override + public boolean isLegacy() { + return true; + } +} diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotNativeWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotNativeWorldManager.java new file mode 100644 index 00000000..f703ecdb --- /dev/null +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotNativeWorldManager.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.platform.spigot.world.manager; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.geysermc.adapters.spigot.SpigotAdapters; +import org.geysermc.adapters.spigot.SpigotWorldAdapter; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; + +public class GeyserSpigotNativeWorldManager extends GeyserSpigotWorldManager { + protected final SpigotWorldAdapter adapter; + + public GeyserSpigotNativeWorldManager(boolean use3dBiomes) { + super(use3dBiomes); + adapter = SpigotAdapters.getWorldAdapter(); + } + + @Override + public int getBlockAt(GeyserSession session, int x, int y, int z) { + Player player = Bukkit.getPlayer(session.getPlayerEntity().getUsername()); + if (player == null) { + return BlockTranslator.JAVA_AIR_ID; + } + return adapter.getBlockAt(player.getWorld(), x, y, z); + } +} diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotWorldManager.java similarity index 59% rename from bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java rename to bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotWorldManager.java index 3493fc25..cd1774ba 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotWorldManager.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.spigot.world; +package org.geysermc.platform.spigot.world.manager; import com.fasterxml.jackson.databind.JsonNode; import com.github.steveice10.mc.protocol.MinecraftConstants; @@ -34,6 +34,7 @@ import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.block.Biome; import org.bukkit.block.Block; +import org.bukkit.block.data.BlockData; import org.bukkit.entity.Player; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; @@ -42,38 +43,22 @@ import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.utils.FileUtils; import org.geysermc.connector.utils.GameRule; import org.geysermc.connector.utils.LanguageUtils; -import us.myles.ViaVersion.api.Pair; -import us.myles.ViaVersion.api.Via; -import us.myles.ViaVersion.api.data.MappingData; -import us.myles.ViaVersion.api.minecraft.Position; -import us.myles.ViaVersion.api.protocol.Protocol; -import us.myles.ViaVersion.api.protocol.ProtocolRegistry; -import us.myles.ViaVersion.api.protocol.ProtocolVersion; -import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.Protocol1_13To1_12_2; -import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.storage.BlockStorage; import java.io.InputStream; -import java.util.List; +/** + * The base world manager to use when there is no supported NMS revision + */ public class GeyserSpigotWorldManager extends GeyserWorldManager { /** * The current client protocol version for ViaVersion usage. */ - private static final int CLIENT_PROTOCOL_VERSION = MinecraftConstants.PROTOCOL_VERSION; + protected static final int CLIENT_PROTOCOL_VERSION = MinecraftConstants.PROTOCOL_VERSION; - /** - * Whether the server is pre-1.13. - */ - private final boolean isLegacy; /** * Whether the server is pre-1.16 and therefore does not support 3D biomes on an API level guaranteed. */ private final boolean use3dBiomes; - /** - * You need ViaVersion to connect to an older server with Geyser. - * However, we still check for ViaVersion in case there's some other way that gets Geyser on a pre-1.13 Bukkit server - */ - private final boolean isViaVersion; /** * Stores a list of {@link Biome} ordinal numbers to Minecraft biome numeric IDs. * @@ -87,10 +72,8 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { */ private final Int2IntMap biomeToIdMap = new Int2IntOpenHashMap(Biome.values().length); - public GeyserSpigotWorldManager(boolean isLegacy, boolean use3dBiomes, boolean isViaVersion) { - this.isLegacy = isLegacy; + public GeyserSpigotWorldManager(boolean use3dBiomes) { this.use3dBiomes = use3dBiomes; - this.isViaVersion = isViaVersion; // Load the values into the biome-to-ID map InputStream biomeStream = FileUtils.getResource("biomes.json"); @@ -116,83 +99,26 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { @Override public int getBlockAt(GeyserSession session, int x, int y, int z) { Player bukkitPlayer; - if ((this.isLegacy && !this.isViaVersion) - || session.getPlayerEntity() == null - || (bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) { + if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) { return BlockTranslator.JAVA_AIR_ID; } World world = bukkitPlayer.getWorld(); - if (isLegacy) { - return getLegacyBlock(session, x, y, z, true); - } - //TODO possibly: detect server version for all versions and use ViaVersion for block state mappings like below - return BlockTranslator.getJavaIdBlockMap().getOrDefault(world.getBlockAt(x, y, z).getBlockData().getAsString(), 0); - } - - public static int getLegacyBlock(GeyserSession session, int x, int y, int z, boolean isViaVersion) { - if (isViaVersion) { - Player bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername()); - // Get block entity storage - BlockStorage storage = Via.getManager().getConnection(bukkitPlayer.getUniqueId()).get(BlockStorage.class); - return getLegacyBlock(storage, bukkitPlayer.getWorld(), x, y, z); - } else { - return BlockTranslator.JAVA_AIR_ID; - } - } - - @SuppressWarnings("deprecation") - public static int getLegacyBlock(BlockStorage storage, World world, int x, int y, int z) { - Block block = world.getBlockAt(x, y, z); - // Black magic that gets the old block state ID - int blockId = (block.getType().getId() << 4) | (block.getData() & 0xF); - // Convert block state from old version (1.12.2) -> 1.13 -> 1.13.1 -> 1.14 -> 1.15 -> 1.16 -> 1.16.2 - blockId = ProtocolRegistry.getProtocol(Protocol1_13To1_12_2.class).getMappingData().getNewBlockId(blockId); - List> protocolList = ProtocolRegistry.getProtocolPath(CLIENT_PROTOCOL_VERSION, - ProtocolVersion.v1_13.getId()); - // Translate block entity differences - some information was stored in block tags and not block states - if (storage.isWelcome(blockId)) { // No getOrDefault method - BlockStorage.ReplacementData data = storage.get(new Position(x, (short) y, z)); - if (data != null && data.getReplacement() != -1) { - blockId = data.getReplacement(); - } - } - for (int i = protocolList.size() - 1; i >= 0; i--) { - MappingData mappingData = protocolList.get(i).getValue().getMappingData(); - if (mappingData != null) { - blockId = mappingData.getNewBlockStateId(blockId); - } - } - return blockId; + return BlockTranslator.getJavaIdBlockMap().getOrDefault(world.getBlockAt(x, y, z).getBlockData().getAsString(), BlockTranslator.JAVA_AIR_ID); } @Override public void getBlocksInSection(GeyserSession session, int x, int y, int z, Chunk chunk) { Player bukkitPlayer; - if ((this.isLegacy && !this.isViaVersion) - || session.getPlayerEntity() == null - || (bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) { + if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) { return; } World world = bukkitPlayer.getWorld(); - if (this.isLegacy) { - // Get block entity storage - BlockStorage storage = Via.getManager().getConnection(bukkitPlayer.getUniqueId()).get(BlockStorage.class); - for (int blockY = 0; blockY < 16; blockY++) { // Cache-friendly iteration order - for (int blockZ = 0; blockZ < 16; blockZ++) { - for (int blockX = 0; blockX < 16; blockX++) { - chunk.set(blockX, blockY, blockZ, getLegacyBlock(storage, world, (x << 4) + blockX, (y << 4) + blockY, (z << 4) + blockZ)); - } - } - } - } else { - //TODO: see above TODO in getBlockAt - for (int blockY = 0; blockY < 16; blockY++) { // Cache-friendly iteration order - for (int blockZ = 0; blockZ < 16; blockZ++) { - for (int blockX = 0; blockX < 16; blockX++) { - Block block = world.getBlockAt((x << 4) + blockX, (y << 4) + blockY, (z << 4) + blockZ); - int id = BlockTranslator.getJavaIdBlockMap().getOrDefault(block.getBlockData().getAsString(), BlockTranslator.JAVA_AIR_ID); - chunk.set(blockX, blockY, blockZ, id); - } + for (int blockY = 0; blockY < 16; blockY++) { // Cache-friendly iteration order + for (int blockZ = 0; blockZ < 16; blockZ++) { + for (int blockX = 0; blockX < 16; blockX++) { + Block block = world.getBlockAt((x << 4) + blockX, (y << 4) + blockY, (z << 4) + blockZ); + int id = BlockTranslator.getJavaIdBlockMap().getOrDefault(block.getBlockData().getAsString(), BlockTranslator.JAVA_AIR_ID); + chunk.set(blockX, blockY, blockZ, id); } } } @@ -254,4 +180,16 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { public boolean hasPermission(GeyserSession session, String permission) { return Bukkit.getPlayer(session.getPlayerEntity().getUsername()).hasPermission(permission); } + + /** + * This must be set to true if we are pre-1.13, and {@link BlockData#getAsString() does not exist}. + * + * This should be set to true if we are post-1.13 but before the latest version, and we should convert the old block state id + * to the current one. + * + * @return whether there is a difference between client block state and server block state that requires extra processing + */ + public boolean isLegacy() { + return false; + } } diff --git a/connector/pom.xml b/connector/pom.xml index 95794ff2..d7017b0d 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -257,6 +257,8 @@