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
This commit is contained in:
Tim203 2020-08-28 17:47:52 +02:00 committed by GitHub
parent 7cbfdcf521
commit 1c84993853
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 549 additions and 616 deletions

View file

@ -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());
}
}

View file

@ -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;
}
}
}

View file

@ -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);

View file

@ -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

View file

@ -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;
}
}
}

View file

@ -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<String, SpongeUserAuthenticationInfo> 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<String> userAuths = new ArrayList<String>(((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<String, SpongeUserAuthenticationInfo> 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
}
}

View file

@ -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;
}
}
}

View file

@ -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());

View file

@ -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());
}
}

View file

@ -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();
}

View file

@ -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<PluginContainer> 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());
}
}

View file

@ -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;
}
}
}

View file

@ -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;

View file

@ -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();
}

View file

@ -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();

View file

@ -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<String, UserAuthenticationInfo> 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;
}

View file

@ -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());
}
}

View file

@ -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");

View file

@ -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) {

View file

@ -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<Objective> 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
*

View file

@ -41,13 +41,11 @@ public class JavaScoreboardObjectiveTranslator extends PacketTranslator<ServerSc
@Override
public void translate(ServerScoreboardObjectivePacket packet, GeyserSession session) {
WorldCache cache = session.getWorldCache();
Scoreboard scoreboard = cache.getScoreboard();
Scoreboard scoreboard = session.getWorldCache().getScoreboard();
Objective objective = scoreboard.getObjective(packet.getName());
if (objective == null && packet.getAction() != ObjectiveAction.REMOVE) {
objective = scoreboard.registerNewObjective(packet.getName(), true);
objective = scoreboard.registerNewObjective(packet.getName(), false);
}
switch (packet.getAction()) {
@ -61,6 +59,8 @@ public class JavaScoreboardObjectiveTranslator extends PacketTranslator<ServerSc
break;
}
if (objective != null && !objective.isTemp()) scoreboard.onUpdate();
if (objective != null && objective.isActive()) {
scoreboard.onUpdate();
}
}
}

View file

@ -28,10 +28,12 @@ package org.geysermc.connector.network.translators.java.scoreboard;
import com.github.steveice10.mc.protocol.packet.ingame.server.scoreboard.ServerTeamPacket;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.GeyserLogger;
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.scoreboard.Scoreboard;
import org.geysermc.connector.scoreboard.ScoreboardUpdater;
import org.geysermc.connector.scoreboard.Team;
import org.geysermc.connector.scoreboard.UpdateType;
import org.geysermc.connector.utils.LanguageUtils;
@ -42,10 +44,15 @@ import java.util.Set;
@Translator(packet = ServerTeamPacket.class)
public class JavaTeamTranslator extends PacketTranslator<ServerTeamPacket> {
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<ServerTeamPacket> {
.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<String> toPlayerSet(String[] players) {
return new ObjectOpenHashSet<>(Arrays.asList(players));
return new ObjectOpenHashSet<>(players);
}
}

View file

@ -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<ServerUpdateScorePacket> {
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();
}
}
}

View file

@ -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<String, Score> scores = new HashMap<>();
private Map<String, Score> 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";
}
}
}

View file

@ -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());
}
}

View file

@ -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<String, Objective> objectives = new HashMap<>();
private Map<String, Team> teams = new HashMap<>();
private final Map<String, Objective> objectives = new ConcurrentHashMap<>();
private final Map<String, Team> 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<String> 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<Objective> changedObjectives = new ObjectOpenHashSet<>();
List<ScoreInfo> addScores = new ArrayList<>();
List<ScoreInfo> removeScores = new ArrayList<>();
List<ScoreInfo> addScores = new ArrayList<>(getLastScoreCount());
List<ScoreInfo> 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<Score> 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();

View file

@ -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();
}
}

View file

@ -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<String> 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();
}
}

View file

@ -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