From 1c849938539ed919f184d4243fd674728ca65127 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Fri, 28 Aug 2020 17:47:52 +0200 Subject: [PATCH] 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