1
0
mirror of https://github.com/GeyserMC/Floodgate.git synced 2025-12-30 12:19:22 +00:00

Merge remote-tracking branch 'origin/dev/2.1.1' into feature/cumulus-1.1

# Conflicts:
#	build-logic/src/main/kotlin/Versions.kt
This commit is contained in:
Tim203
2022-06-06 10:47:48 +02:00
82 changed files with 2149 additions and 1813 deletions

View File

@@ -40,13 +40,14 @@ import org.geysermc.floodgate.api.inject.PlatformInjector;
import org.geysermc.floodgate.api.link.PlayerLink;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.api.packet.PacketHandlers;
import org.geysermc.floodgate.config.ConfigLoader;
import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.config.FloodgateConfigHolder;
import org.geysermc.floodgate.config.loader.ConfigLoader;
import org.geysermc.floodgate.link.PlayerLinkLoader;
import org.geysermc.floodgate.module.ConfigLoadedModule;
import org.geysermc.floodgate.module.PostInitializeModule;
import org.geysermc.floodgate.news.NewsChecker;
import org.geysermc.floodgate.util.Metrics;
import org.geysermc.floodgate.util.PrefixCheckTask;
public class FloodgatePlatform {
@@ -100,8 +101,6 @@ public class FloodgatePlatform {
InstanceHolder.set(api, link, this.injector, packetHandlers, handshakeHandlers, KEY);
// todo provide build number and branch for Geyser dump
guice.getInstance(NewsChecker.class).start();
}
@@ -125,6 +124,8 @@ public class FloodgatePlatform {
PrefixCheckTask.checkAndExecuteDelayed(config, logger);
guice.getInstance(Metrics.class);
return true;
}
@@ -139,6 +140,7 @@ public class FloodgatePlatform {
}
}
guice.getInstance(NewsChecker.class).shutdown();
api.getPlayerLink().stop();
return true;
}

View File

@@ -70,7 +70,7 @@ public class HandshakeDataImpl implements HandshakeData {
int usernameLength = Math.min(bedrockData.getUsername().length(), 16 - prefix.length());
javaUsername = prefix + bedrockData.getUsername().substring(0, usernameLength);
if (config.isReplaceSpaces()) {
javaUsername = javaUsername.replaceAll(" ", "_");
javaUsername = javaUsername.replace(" ", "_");
}
javaUniqueId = Utils.getJavaUuid(bedrockData.getXuid());

View File

@@ -35,20 +35,20 @@ import cloud.commandframework.context.CommandContext;
import com.google.inject.Inject;
import lombok.Getter;
import lombok.NoArgsConstructor;
import net.kyori.adventure.text.Component;
import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.api.link.LinkRequestResult;
import org.geysermc.floodgate.api.link.PlayerLink;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.command.util.Permission;
import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.link.GlobalPlayerLinking;
import org.geysermc.floodgate.platform.command.FloodgateCommand;
import org.geysermc.floodgate.platform.command.TranslatableMessage;
import org.geysermc.floodgate.player.UserAudience;
import org.geysermc.floodgate.player.UserAudience.PlayerAudience;
import org.geysermc.floodgate.player.UserAudienceArgument;
import org.geysermc.floodgate.player.audience.ProfileAudience;
import org.geysermc.floodgate.player.audience.ProfileAudienceArgument;
import org.geysermc.floodgate.util.Constants;
import org.geysermc.floodgate.util.Permissions;
@NoArgsConstructor
public final class LinkAccountCommand implements FloodgateCommand {
@@ -60,8 +60,8 @@ public final class LinkAccountCommand implements FloodgateCommand {
return commandManager.commandBuilder("linkaccount",
ArgumentDescription.of("Link your Java account with your Bedrock account"))
.senderType(PlayerAudience.class)
.permission(Permissions.COMMAND_LINK.get())
.argument(UserAudienceArgument.of("player", true))
.permission(Permission.COMMAND_LINK.get())
.argument(ProfileAudienceArgument.of("player", true))
.argument(StringArgument.optional("code"))
.handler(this::execute)
.build();
@@ -90,6 +90,10 @@ public final class LinkAccountCommand implements FloodgateCommand {
return;
}
ProfileAudience targetUser = context.get("player");
// allowUuid is false so username cannot be null
String targetName = targetUser.username();
// when the player is a Bedrock player
if (api.isFloodgatePlayer(sender.uuid())) {
if (!context.contains("code")) {
@@ -97,8 +101,6 @@ public final class LinkAccountCommand implements FloodgateCommand {
return;
}
UserAudience targetUser = context.get("player");
String targetName = targetUser.username();
String code = context.get("code");
link.verifyLinkRequest(sender.uuid(), targetName, sender.username(), code)
@@ -125,7 +127,7 @@ public final class LinkAccountCommand implements FloodgateCommand {
sender.disconnect(Message.LINK_REQUEST_COMPLETED, targetName);
break;
default:
sender.disconnect(Component.text("Invalid account linking result"));
sender.disconnect("Invalid account linking result");
break;
}
});
@@ -137,9 +139,6 @@ public final class LinkAccountCommand implements FloodgateCommand {
return;
}
UserAudience targetUser = context.get("player");
String targetName = targetUser.username();
link.createLinkRequest(sender.uuid(), sender.username(), targetName)
.whenComplete((result, throwable) -> {
if (throwable != null || result == LinkRequestResult.UNKNOWN_ERROR) {

View File

@@ -28,7 +28,6 @@ package org.geysermc.floodgate.command;
import cloud.commandframework.Command;
import cloud.commandframework.CommandManager;
import cloud.commandframework.context.CommandContext;
import net.kyori.adventure.text.Component;
import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.platform.command.FloodgateCommand;
@@ -47,7 +46,7 @@ public class TestCommand implements FloodgateCommand {
@Override
public void execute(CommandContext<UserAudience> context) {
int players = FloodgateApi.getInstance().getPlayers().size();
context.getSender().sendMessage(Component.text(players));
context.getSender().sendMessage(String.valueOf(players));
}
@Override

View File

@@ -43,7 +43,7 @@ import org.geysermc.floodgate.platform.command.TranslatableMessage;
import org.geysermc.floodgate.player.UserAudience;
import org.geysermc.floodgate.player.UserAudience.PlayerAudience;
import org.geysermc.floodgate.util.Constants;
import org.geysermc.floodgate.util.Permissions;
import org.geysermc.floodgate.command.util.Permission;
@NoArgsConstructor
public final class UnlinkAccountCommand implements FloodgateCommand {
@@ -54,7 +54,7 @@ public final class UnlinkAccountCommand implements FloodgateCommand {
return commandManager.commandBuilder("unlinkaccount",
ArgumentDescription.of("Unlink your Java account from your Bedrock account"))
.senderType(PlayerAudience.class)
.permission(Permissions.COMMAND_UNLINK.get())
.permission(Permission.COMMAND_UNLINK.get())
.handler(this::execute)
.build();
}

View File

@@ -38,17 +38,18 @@ import java.util.UUID;
import lombok.Getter;
import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.command.util.Permission;
import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.config.ProxyFloodgateConfig;
import org.geysermc.floodgate.platform.command.CommandUtil;
import org.geysermc.floodgate.platform.command.FloodgateCommand;
import org.geysermc.floodgate.platform.command.TranslatableMessage;
import org.geysermc.floodgate.platform.util.PlayerType;
import org.geysermc.floodgate.player.UserAudience;
import org.geysermc.floodgate.player.UserAudienceArgument;
import org.geysermc.floodgate.player.UserAudienceArgument.PlayerType;
import org.geysermc.floodgate.player.audience.ProfileAudience;
import org.geysermc.floodgate.player.audience.ProfileAudienceArgument;
import org.geysermc.floodgate.util.Constants;
import org.geysermc.floodgate.util.HttpUtils;
import org.geysermc.floodgate.util.Permissions;
public class WhitelistCommand implements FloodgateCommand {
@Inject private FloodgateConfig config;
@@ -58,25 +59,25 @@ public class WhitelistCommand implements FloodgateCommand {
public Command<UserAudience> buildCommand(CommandManager<UserAudience> commandManager) {
Command.Builder<UserAudience> builder = commandManager.commandBuilder("fwhitelist",
ArgumentDescription.of("Easy way to whitelist Bedrock players"))
.permission(Permissions.COMMAND_WHITELIST.get());
.permission(Permission.COMMAND_WHITELIST.get());
commandManager.command(builder
.literal("add", "a")
.argument(UserAudienceArgument.of("player", true, true, PlayerType.ONLY_BEDROCK))
.argument(ProfileAudienceArgument.of("player", true, true, PlayerType.ONLY_BEDROCK))
.handler(context -> performCommand(context, true)));
return builder
.literal("remove", "r")
.argument(UserAudienceArgument.of("player", true, true, PlayerType.ONLY_BEDROCK))
.argument(ProfileAudienceArgument.of("player", true, true, PlayerType.ONLY_BEDROCK))
.handler(context -> performCommand(context, false))
.build();
}
public void performCommand(CommandContext<UserAudience> context, boolean add) {
UserAudience sender = context.getSender();
UserAudience player = context.get("player");
UUID uuid = player.uuid();
String name = player.username();
ProfileAudience profile = context.get("player");
UUID uuid = profile.uuid();
String name = profile.username();
if (name == null && uuid == null) {
sender.sendMessage(Message.UNEXPECTED_ERROR);

View File

@@ -35,9 +35,9 @@ import cloud.commandframework.context.CommandContext;
import java.util.Locale;
import java.util.function.Consumer;
import lombok.RequiredArgsConstructor;
import org.geysermc.floodgate.command.util.Permission;
import org.geysermc.floodgate.platform.command.FloodgateCommand;
import org.geysermc.floodgate.player.UserAudience;
import org.geysermc.floodgate.util.Permissions;
public final class MainCommand implements FloodgateCommand {
@Override
@@ -46,12 +46,13 @@ public final class MainCommand implements FloodgateCommand {
"floodgate",
ArgumentDescription.of("A set of Floodgate related actions in one command"))
.senderType(UserAudience.class)
.permission(Permissions.COMMAND_MAIN.get())
.permission(Permission.COMMAND_MAIN.get())
.handler(this::execute);
for (SubCommand subCommand : SubCommand.VALUES) {
commandManager.command(builder
.literal(subCommand.name().toLowerCase(Locale.ROOT), subCommand.description)
.permission(subCommand.permission.get())
.handler(subCommand.executor::accept)
);
}
@@ -65,10 +66,12 @@ public final class MainCommand implements FloodgateCommand {
StringBuilder helpMessage = new StringBuilder("Available subcommands are:\n");
for (SubCommand subCommand : SubCommand.VALUES) {
helpMessage.append('\n').append(COLOR_CHAR).append('b')
.append(subCommand.name().toLowerCase(Locale.ROOT))
.append(COLOR_CHAR).append("f - ").append(COLOR_CHAR).append('7')
.append(subCommand.description);
if (context.getSender().hasPermission(subCommand.permission.get())) {
helpMessage.append('\n').append(COLOR_CHAR).append('b')
.append(subCommand.name().toLowerCase(Locale.ROOT))
.append(COLOR_CHAR).append("f - ").append(COLOR_CHAR).append('7')
.append(subCommand.description);
}
}
context.getSender().sendMessage(helpMessage.toString());
@@ -77,11 +80,12 @@ public final class MainCommand implements FloodgateCommand {
@RequiredArgsConstructor
enum SubCommand {
FIREWALL("Check if your outgoing firewall allows Floodgate to work properly",
FirewallCheckSubcommand::executeFirewall);
Permission.COMMAND_MAIN_FIREWALL, FirewallCheckSubcommand::executeFirewall);
static final SubCommand[] VALUES = values();
final String description;
final Permission permission;
final Consumer<CommandContext<UserAudience>> executor;
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.command.util;
import static org.geysermc.floodgate.command.util.PermissionDefault.OP;
import static org.geysermc.floodgate.command.util.PermissionDefault.TRUE;
public enum Permission {
COMMAND_MAIN("floodgate.command.floodgate", TRUE),
COMMAND_MAIN_FIREWALL(COMMAND_MAIN, "firewall", OP),
COMMAND_LINK("floodgate.command.linkaccount", TRUE),
COMMAND_UNLINK("floodgate.command.unlinkaccount", TRUE),
COMMAND_WHITELIST("floodgate.command.fwhitelist", OP),
NEWS_RECEIVE("floodgate.news.receive", OP);
private final String permission;
private final PermissionDefault defaultValue;
Permission(String permission, PermissionDefault defaultValue) {
this.permission = permission;
this.defaultValue = defaultValue;
}
Permission(Permission parent, String child, PermissionDefault defaultValue) {
this(parent.get() + "." + child, defaultValue);
}
public String get() {
return permission;
}
public PermissionDefault defaultValue() {
return defaultValue;
}
}

View File

@@ -23,23 +23,8 @@
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.player;
package org.geysermc.floodgate.command.util;
import java.util.UUID;
import net.kyori.adventure.audience.Audience;
import org.checkerframework.checker.index.qual.NonNegative;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.floodgate.player.UserAudience.ConsoleAudience;
public interface ServerAudience extends Audience {
@NonNull Iterable<? extends UserAudience> onlineAudiences();
@NonNull ConsoleAudience consoleAudience();
@NonNegative int onlineCount();
@Nullable UserAudience audienceOf(final @NonNull UUID uuid);
@Nullable UserAudience audienceOf(final @NonNull String username);
public enum PermissionDefault {
TRUE, FALSE, OP, NOT_OP
}

View File

@@ -23,26 +23,27 @@
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.config.loader;
package org.geysermc.floodgate.config;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.Key;
import java.util.UUID;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.geysermc.configutils.ConfigUtilities;
import org.geysermc.configutils.file.codec.PathFileCodec;
import org.geysermc.configutils.file.template.ResourceTemplateReader;
import org.geysermc.configutils.updater.change.Changes;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.config.ProxyFloodgateConfig;
import org.geysermc.floodgate.config.updater.ConfigUpdater;
import org.geysermc.floodgate.crypto.FloodgateCipher;
import org.geysermc.floodgate.crypto.KeyProducer;
@Getter
@RequiredArgsConstructor
public final class ConfigLoader {
private final Path dataFolder;
private final Class<? extends FloodgateConfig> configClass;
private final DefaultConfigHandler configCreator;
private final ConfigUpdater updater;
private final KeyProducer keyProducer;
private final FloodgateCipher cipher;
@@ -51,61 +52,42 @@ public final class ConfigLoader {
@SuppressWarnings("unchecked")
public <T extends FloodgateConfig> T load() {
Path configPath = dataFolder.resolve("config.yml");
String defaultConfigName = "config.yml";
boolean proxy = ProxyFloodgateConfig.class.isAssignableFrom(configClass);
if (proxy) {
defaultConfigName = "proxy-" + defaultConfigName;
String templateFile = "config.yml";
if (ProxyFloodgateConfig.class.isAssignableFrom(configClass)) {
templateFile = "proxy-" + templateFile;
}
boolean newConfig = !Files.exists(configPath);
if (newConfig) {
try {
configCreator.createDefaultConfig(defaultConfigName, configPath);
} catch (Exception exception) {
logger.error("Error while creating config", exception);
}
}
//todo old Floodgate logged a message when version = 0 and it generated a new key.
// Might be nice to allow you to run a function for a specific version.
// it would also be nice to have sections in versionBuilder so that you don't have to
// provide the path all the time
ConfigUtilities utilities =
ConfigUtilities.builder()
.fileCodec(PathFileCodec.of(dataFolder))
.configFile("config.yml")
.templateReader(ResourceTemplateReader.of(getClass()))
.template(templateFile)
.changes(Changes.builder()
.version(1, Changes.versionBuilder()
.keyRenamed("player-link.enable", "player-link.enabled")
.keyRenamed("player-link.allow-linking", "player-link.allowed"))
.version(2, Changes.versionBuilder()
.keyRenamed("player-link.use-global-linking", "player-link.enable-global-linking"))
.build())
.definePlaceholder("metrics.uuid", UUID::randomUUID)
.postInitializeCallbackArgument(this)
.build();
T configInstance;
try {
// check and update if the config is outdated
if (!newConfig) {
updater.update(this, defaultConfigName);
}
FloodgateConfig config = ConfigInitializer.initializeFrom(
Files.newInputStream(configPath), configClass);
try {
configInstance = (T) config;
} catch (ClassCastException exception) {
logger.error("Failed to cast config file to required class.", exception);
throw new RuntimeException(exception);
}
} catch (Exception exception) {
logger.error("Error while loading config", exception);
return (T) utilities.executeOn(configClass);
} catch (Throwable throwable) {
throw new RuntimeException(
"Failed to load the config! Try to delete the config file", exception);
"Failed to load the config! Try to delete the config file if this error persists",
throwable
);
}
Path keyPath = dataFolder.resolve(configInstance.getKeyFileName());
// don't assume that the key always exists with the existence of a config
if (!Files.exists(keyPath)) {
generateKey(keyPath);
}
try {
Key key = keyProducer.produceFrom(keyPath);
cipher.init(key);
configInstance.setKey(key);
} catch (IOException exception) {
logger.error("Error while reading the key", exception);
throw new RuntimeException("Failed to read the key!", exception);
}
return configInstance;
}
public void generateKey(Path keyPath) {

View File

@@ -25,15 +25,20 @@
package org.geysermc.floodgate.config;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.Key;
import lombok.Getter;
import org.geysermc.configutils.loader.callback.CallbackResult;
import org.geysermc.configutils.loader.callback.GenericPostInitializeCallback;
/**
* The global Floodgate configuration file used in every platform. Some platforms have their own
* addition to the global configuration like {@link ProxyFloodgateConfig} for the proxies.
*/
@Getter
public class FloodgateConfig {
public class FloodgateConfig implements GenericPostInitializeCallback<ConfigLoader> {
private String keyFileName;
private String usernamePrefix;
private boolean replaceSpaces;
@@ -42,22 +47,36 @@ public class FloodgateConfig {
private DisconnectMessages disconnect;
private PlayerLinkConfig playerLink;
private MetricsConfig metrics;
private boolean debug;
private int configVersion;
private Key key;
public void setKey(Key key) {
if (this.key == null) {
this.key = key;
}
}
public boolean isProxy() {
return this instanceof ProxyFloodgateConfig;
}
@Override
public CallbackResult postInitialize(ConfigLoader loader) {
Path keyPath = loader.getDataFolder().resolve(getKeyFileName());
// don't assume that the key always exists with the existence of a config
if (!Files.exists(keyPath)) {
loader.generateKey(keyPath);
}
try {
Key floodgateKey = loader.getKeyProducer().produceFrom(keyPath);
loader.getCipher().init(floodgateKey);
key = floodgateKey;
} catch (IOException exception) {
return CallbackResult.failed(exception.getMessage());
}
return CallbackResult.ok();
}
@Getter
public static class DisconnectMessages {
private String invalidKey;
@@ -68,10 +87,16 @@ public class FloodgateConfig {
public static class PlayerLinkConfig {
private boolean enabled;
private boolean requireLink;
private boolean enableOwnLinking = false;
private boolean enableOwnLinking;
private boolean allowed;
private long linkCodeTimeout;
private String type;
private boolean enableGlobalLinking;
}
@Getter
public static class MetricsConfig {
private boolean enabled;
private String uuid;
}
}

View File

@@ -1,109 +0,0 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.config.loader;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.LinkedHashMap;
import java.util.Map;
import org.geysermc.floodgate.config.FloodgateConfig;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor;
import org.yaml.snakeyaml.constructor.CustomClassLoaderConstructor;
import org.yaml.snakeyaml.introspector.BeanAccess;
import org.yaml.snakeyaml.introspector.FieldProperty;
import org.yaml.snakeyaml.introspector.Property;
import org.yaml.snakeyaml.introspector.PropertyUtils;
public class ConfigInitializer {
private static final Yaml YAML;
static {
Constructor constructor =
new CustomClassLoaderConstructor(ConfigInitializer.class.getClassLoader());
constructor.setPropertyUtils(new PropertyUtils() {
@Override
protected Map<String, Property> getPropertiesMap(Class<?> type, BeanAccess bAccess) {
Map<String, Property> properties = new LinkedHashMap<>();
getPropertiesFromClass(type, FloodgateConfig.class, properties);
return properties;
}
private void getPropertiesFromClass(
Class<?> type,
Class<?> stopAfter,
Map<String, Property> propertyMap) {
Class<?> current = type;
while (!Object.class.equals(current)) {
for (Field field : current.getDeclaredFields()) {
int modifiers = field.getModifiers();
if (!Modifier.isStatic(modifiers) && !Modifier.isTransient(modifiers)) {
String correctName = getCorrectName(field.getName());
// children should override parents
propertyMap.putIfAbsent(correctName, new FieldProperty(field));
}
if (field.getClass().getSuperclass().equals(current)) {
getPropertiesFromClass(field.getClass(), field.getClass(), propertyMap);
}
}
if (current.equals(stopAfter)) {
return;
}
current = type.getSuperclass();
}
}
private String getCorrectName(String name) {
// convert sendFloodgateData to send-floodgate-data,
// which is the style of writing config fields
StringBuilder propertyBuilder = new StringBuilder();
for (int i = 0; i < name.length(); i++) {
char current = name.charAt(i);
if (Character.isUpperCase(current)) {
propertyBuilder.append('-').append(Character.toLowerCase(current));
} else {
propertyBuilder.append(current);
}
}
return propertyBuilder.toString();
}
});
constructor.getPropertyUtils().setSkipMissingProperties(true);
YAML = new Yaml(constructor);
}
public static <T extends FloodgateConfig> T initializeFrom(
InputStream dataStream,
Class<T> configClass) {
return YAML.loadAs(dataStream, configClass);
}
}

View File

@@ -1,159 +0,0 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.config.loader;
import static org.geysermc.floodgate.util.MessageFormatter.format;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import org.geysermc.floodgate.util.Utils;
public class DefaultConfigHandler {
public void createDefaultConfig(String defaultConfigLocation, Path configPath) throws IOException {
List<String> configLines = loadDefaultConfig(defaultConfigLocation);
// writing the new config file
Files.write(configPath, configLines);
}
public List<String> loadDefaultConfig(String defaultConfigLocation)
throws IOException {
List<String> lines = Utils.readAllLines(defaultConfigLocation);
List<String> configLines = new ArrayList<>();
String parentConfig = null;
List<String> parentLines = null;
int lastInsertLine = -1;
int tempAddAfter = -1;
for (String line : lines) {
// >>(space) or >>|
if (line.startsWith(">>")) {
if (line.length() >= 3) {
// define parent file
if (line.charAt(2) == ' ') {
if (tempAddAfter != -1) {
throw new IllegalStateException(
"Cannot define new parent without closing the current section");
}
parentConfig = line.substring(3);
parentLines = null;
lastInsertLine = -1;
continue;
}
// define start / end of insert section
if (line.charAt(2) == '|') {
// end section
if (line.length() == 3) {
if (tempAddAfter == -1) {
throw new IllegalStateException("Cannot close an unclosed section");
}
lastInsertLine = tempAddAfter;
tempAddAfter = -1;
continue;
}
// start insert section
if (parentConfig == null) {
throw new IllegalStateException(
"Cannot start insert section without providing a parent");
}
if (tempAddAfter != -1) {
throw new IllegalStateException(
"Cannot start section with an unclosed section");
}
// note that addAfter starts counting from 1
int addAfter = Integer.parseInt(line.substring(4)) - 1;
if (lastInsertLine > -1 && addAfter < lastInsertLine) {
throw new IllegalStateException(format(
"Cannot add the same lines twice {} {}",
addAfter, lastInsertLine
));
}
// as you can see by this implementation
// we don't support parent files in parent files
if (lastInsertLine == -1) {
parentLines = Utils.readAllLines(parentConfig);
for (int i = 0; i <= addAfter; i++) {
configLines.add(parentLines.get(i));
}
} else {
for (int i = lastInsertLine; i <= addAfter; i++) {
configLines.add(parentLines.get(i));
}
}
tempAddAfter = addAfter;
continue;
}
if (line.charAt(2) == '*') {
if (parentConfig == null) {
throw new IllegalStateException(
"Cannot write rest of the parent without providing a parent");
}
if (tempAddAfter != -1) {
throw new IllegalStateException(
"Cannot write rest of the parent config while an insert section is still open");
}
if (lastInsertLine == -1) {
parentLines = Utils.readAllLines(parentConfig);
configLines.addAll(parentLines);
continue;
}
// the lastInsertLine has already been printed, so we won't print it twice
for (int i = lastInsertLine + 1; i < parentLines.size(); i++) {
configLines.add(parentLines.get(i));
}
continue;
}
throw new IllegalStateException(
"The use of >>" + line.charAt(2) + " is unknown");
}
throw new IllegalStateException("Unable do something with just >>");
}
// everything else: comments and key/value lines will be added
configLines.add(line);
}
return configLines;
}
}

View File

@@ -1,182 +0,0 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.config.updater;
import com.google.common.base.Ascii;
import com.google.inject.Inject;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.config.loader.DefaultConfigHandler;
public final class ConfigFileUpdater {
@Inject private FloodgateLogger logger;
@Inject private DefaultConfigHandler defaultConfigHandler;
/**
* Simple config file updater. Please note that all the keys should be unique and that this
* system wasn't made for complex configurations.
*
* @param configLocation the location of the Floodgate config
* @param currentVersion the key value map of the current config
* @param renames name changes introduced in this version. new (key) to old
* (value)
* @param defaultConfigLocation the location of the default Floodgate config
* @throws IOException if an I/O error occurs
*/
public void update(
Path configLocation,
Map<String, Object> currentVersion,
Map<String, String> renames,
String defaultConfigLocation)
throws IOException {
List<String> notFound = new ArrayList<>();
List<String> newConfig = defaultConfigHandler.loadDefaultConfig(defaultConfigLocation);
String spaces = "";
Map<String, Object> map = null;
String line;
for (int i = 0; i < newConfig.size(); i++) {
line = newConfig.get(i);
// we don't have to check comments or empty lines
if (line.isEmpty() || line.charAt(0) == '#') {
continue;
}
StringBuilder currentSpaces = new StringBuilder();
while (line.charAt(currentSpaces.length()) == Ascii.SPACE) {
currentSpaces.append(Ascii.SPACE);
}
// end of subcategory
if (!spaces.isEmpty() && currentSpaces.length() < spaces.length()) {
// we can assume this since we don't allow subcategories of subcategories
spaces = "";
map = null;
}
// ignore comments
if (line.charAt(currentSpaces.length()) == '#') {
continue;
}
int splitIndex = line.indexOf(':');
// if the line has a 'key: value' structure
if (splitIndex != -1) {
// start of a subcategory
if (line.length() == splitIndex + 1) {
if (currentSpaces.length() > 0) {
throw new IllegalStateException(
"Config too complex! I can't understand subcategories of a subcategory");
}
spaces = " ";
//todo allow rename of subcategory?
//noinspection unchecked
map = (Map<String, Object>) currentVersion.get(line.substring(0, splitIndex));
continue;
}
String name = line.substring(spaces.length(), splitIndex);
// don't change the config-version to the old value!
if (name.equals("config-version")) {
continue;
}
// allow multiple renames
String tempName;
String oldName = name;
do {
tempName = oldName;
oldName = renames.getOrDefault(oldName, oldName);
} while (!oldName.equals(tempName));
Object value;
if (map != null) {
value = map.get(oldName);
} else {
value = currentVersion.get(spaces + oldName);
}
// use default value if the key doesn't exist in the current version
if (value == null) {
notFound.add(name);
continue;
}
if (value instanceof String) {
String v = (String) value;
if (!v.startsWith("\"") || !v.endsWith("\"")) {
value = "\"" + value + "\"";
}
//todo this doesn't update {0} {1} to {} {} e.g.
}
logger.debug(name + " has been changed to " + value);
newConfig.set(i, spaces + name + ": " + value);
}
}
Files.deleteIfExists(configLocation.getParent().resolve("config-old.yml"));
Files.copy(configLocation, configLocation.getParent().resolve("config-old.yml"));
Files.write(configLocation, newConfig);
logger.info("Successfully updated the config file! " +
"Your old config has been moved to config-old.yml");
if (!notFound.isEmpty()) {
StringBuilder messageBuilder = new StringBuilder(
"Please note that the following keys we not found in the old config and " +
"are now using the default Floodgate config value. Missing/new keys: ");
boolean first = true;
for (String value : notFound) {
if (!first) {
messageBuilder.append(", ");
}
String renamed = renames.get(value);
if (renamed != null) {
messageBuilder.append(renamed).append(" to ");
}
messageBuilder.append(value);
first = false;
}
logger.info(messageBuilder.toString());
}
}
}

View File

@@ -1,122 +0,0 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.config.updater;
import static com.google.common.base.Preconditions.checkArgument;
import static org.geysermc.floodgate.util.MessageFormatter.format;
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.config.loader.ConfigLoader;
import org.yaml.snakeyaml.Yaml;
@RequiredArgsConstructor
public final class ConfigUpdater {
private static final int CONFIG_VERSION = 2;
private final Path dataFolder;
private final ConfigFileUpdater fileUpdater;
private final FloodgateLogger logger;
public void update(ConfigLoader loader, String defaultConfigLocation) {
Path configLocation = dataFolder.resolve("config.yml");
Map<String, Object> config;
try (BufferedReader configReader = Files.newBufferedReader(configLocation)) {
config = new Yaml().load(configReader);
} catch (IOException exception) {
logger.error("Error while opening the config file", exception);
throw new RuntimeException("Failed to update config", exception);
}
// new name -> old name
Map<String, String> renames = new HashMap<>();
int version = 0; // pre-rewrite is the default config version
Object versionElement = config.get("config-version");
// only rewrite configs have a config-version
if (versionElement == null) {
logger.warn("We've detected a pre-rewrite config file, please note that Floodgate " +
"doesn't not work properly if you don't update your Floodgate key used on " +
"all your servers (including Geyser). We'll try to update your Floodgate " +
"config now and we'll also generate a new Floodgate key for you, but if " +
"you're running a network or if you're running a Spigot server with " +
"Geyser Standalone please update as you'll no longer be able to connect.");
renames.put("enabled", "enable"); //todo make dump system and add a boolean 'found-legacy-key' or something like that
renames.put("allowed", "allow-linking");
// relocate the old key so that they can restore it if it was a new key
Path keyFilePath = dataFolder.resolve((String) config.get("key-file-name"));
if (Files.exists(keyFilePath)) {
try {
Files.copy(keyFilePath, dataFolder.resolve("old-key.pem"));
} catch (IOException exception) {
throw new RuntimeException(
"Failed to relocate the old key to make place for a new key",
exception);
}
}
loader.generateKey(keyFilePath);
} else {
// get (and verify) the config version
checkArgument(
versionElement instanceof Integer,
"Config version should be an integer. Did someone mess with the config?"
);
version = (int) versionElement;
checkArgument(
version > 0 && version <= CONFIG_VERSION,
format("Config is newer then possible on this version! Expected {}, got {}",
CONFIG_VERSION, version));
}
// config is already up-to-date
if (version == CONFIG_VERSION) {
return;
}
if (version < 2) {
// renamed 'use-global-linking' to 'enable-global-linking'
// and added 'enable-own-linking'
renames.put("enable-global-linking", "use-global-linking");
}
try {
fileUpdater.update(configLocation, config, renames, defaultConfigLocation);
} catch (IOException exception) {
logger.error("Error while updating the config file", exception);
throw new RuntimeException("Failed to update config", exception);
}
}
}

View File

@@ -77,7 +77,7 @@ public abstract class CommonPlatformInjector implements PlatformInjector {
}
/**
* Method to loop throguh all the addons and call {@link InjectorAddon#onChannelClosed(Channel)}
* Method to loop through all the addons and call {@link InjectorAddon#onChannelClosed(Channel)}
* if {@link InjectorAddon#shouldInject()}
*
* @param channel the channel that was injected

View File

@@ -40,12 +40,9 @@ import org.geysermc.floodgate.api.inject.PlatformInjector;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.api.packet.PacketHandlers;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.config.ConfigLoader;
import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.config.FloodgateConfigHolder;
import org.geysermc.floodgate.config.loader.ConfigLoader;
import org.geysermc.floodgate.config.loader.DefaultConfigHandler;
import org.geysermc.floodgate.config.updater.ConfigFileUpdater;
import org.geysermc.floodgate.config.updater.ConfigUpdater;
import org.geysermc.floodgate.crypto.AesCipher;
import org.geysermc.floodgate.crypto.AesKeyProducer;
import org.geysermc.floodgate.crypto.Base64Topping;
@@ -105,27 +102,10 @@ public class CommonModule extends AbstractModule {
@Singleton
public ConfigLoader configLoader(
@Named("configClass") Class<? extends FloodgateConfig> configClass,
DefaultConfigHandler defaultConfigHandler,
ConfigUpdater configUpdater,
KeyProducer producer,
FloodgateCipher cipher,
FloodgateLogger logger) {
return new ConfigLoader(dataDirectory, configClass, defaultConfigHandler, configUpdater,
producer, cipher, logger);
}
@Provides
@Singleton
public DefaultConfigHandler defaultConfigCreator() {
return new DefaultConfigHandler();
}
@Provides
@Singleton
public ConfigUpdater configUpdater(
ConfigFileUpdater configFileUpdater,
FloodgateLogger logger) {
return new ConfigUpdater(dataDirectory, configFileUpdater, logger);
return new ConfigLoader(dataDirectory, configClass, producer, cipher, logger);
}
@Provides

View File

@@ -43,7 +43,7 @@ import org.geysermc.floodgate.platform.command.CommandUtil;
import org.geysermc.floodgate.util.Constants;
import org.geysermc.floodgate.util.HttpUtils;
import org.geysermc.floodgate.util.HttpUtils.HttpResponse;
import org.geysermc.floodgate.util.Permissions;
import org.geysermc.floodgate.command.util.Permission;
public class NewsChecker {
private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
@@ -72,8 +72,6 @@ public class NewsChecker {
}
private void checkNews() {
// todo also check news for the downloaded database types
HttpResponse<JsonArray> response =
HttpUtils.getSilent(
Constants.NEWS_OVERVIEW_URL + Constants.NEWS_PROJECT_NAME,
@@ -121,14 +119,14 @@ public class NewsChecker {
return;
}
if (commandUtil.hasPermission(player, Permissions.NEWS_RECEIVE.get())) {
if (commandUtil.hasPermission(player, Permission.NEWS_RECEIVE.get())) {
String message = Constants.COLOR_CHAR + "a " + news.getMessage();
commandUtil.sendMessage(player, message);
}
break;
case BROADCAST_TO_OPERATORS:
Collection<Object> onlinePlayers = commandUtil.getOnlinePlayersWithPermission(
Permissions.NEWS_RECEIVE.get()
Permission.NEWS_RECEIVE.get()
);
for (Object onlinePlayer : onlinePlayers) {
@@ -182,7 +180,8 @@ public class NewsChecker {
schedule(delayMs > 0 ? delayMs : 0);
break;
case CONFIG_SPECIFIC:
//todo
//todo this can replace the downloaded database types update check.
// check if ConfigUtils has a way to check this easily
break;
}
activeNewsItems.put(item.getId(), item);

View File

@@ -25,30 +25,118 @@
package org.geysermc.floodgate.platform.command;
import static org.geysermc.floodgate.platform.util.PlayerType.ALL_PLAYERS;
import static org.geysermc.floodgate.platform.util.PlayerType.ONLY_BEDROCK;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.platform.util.PlayerType;
import org.geysermc.floodgate.player.UserAudience;
import org.geysermc.floodgate.player.UserAudienceArgument.PlayerType;
import org.geysermc.floodgate.player.audience.ProfileAudience;
import org.geysermc.floodgate.util.LanguageManager;
import org.geysermc.floodgate.util.Utils;
/**
* An interface used across all Floodgate platforms to simple stuff in commands like kicking players
* and sending player messages independent of the Floodgate platform implementation.
*/
public interface CommandUtil {
@NonNull UserAudience getAudience(final @NonNull Object source);
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
public abstract class CommandUtil {
protected final LanguageManager manager;
protected final FloodgateApi api;
@Nullable UserAudience getAudienceByUuid(final @NonNull UUID uuid);
public abstract @NonNull UserAudience getUserAudience(@NonNull Object source);
@NonNull UserAudience getOfflineAudienceByUuid(final @NonNull UUID uuid);
/**
* Get a ProfileAudience from a source. The source should be a platform-specific player instance
* when the player is online, and the username / uuid of the requested player when offline.
*
* @param source source to create a ProfileAudience from
* @param allowOffline if offline players are allowed
* @return a ProfileAudience unless allowOffline is false and the player isn't online
*/
public @Nullable ProfileAudience getProfileAudience(
@NonNull Object source,
boolean allowOffline) {
Objects.requireNonNull(source);
@Nullable UserAudience getAudienceByUsername(final @NonNull String username);
if (source instanceof UUID) {
return allowOffline ? new ProfileAudience((UUID) source, null) : null;
} else if (source instanceof String) {
return allowOffline ? new ProfileAudience(null, (String) source) : null;
} else {
return new ProfileAudience(getUuidFromSource(source), getUsernameFromSource(source));
}
}
@NonNull UserAudience getOfflineAudienceByUsername(final @NonNull String username);
protected abstract String getUsernameFromSource(@NonNull Object source);
protected abstract UUID getUuidFromSource(@NonNull Object source);
@NonNull Collection<String> getOnlineUsernames(final @NonNull PlayerType limitTo);
protected abstract Collection<?> getOnlinePlayers();
public @NonNull Collection<String> getOnlineUsernames(@NonNull PlayerType limitTo) {
Collection<?> players = getOnlinePlayers();
Collection<String> usernames = new ArrayList<>();
switch (limitTo) {
case ALL_PLAYERS:
for (Object player : players) {
usernames.add(getUsernameFromSource(player));
}
break;
case ONLY_JAVA:
for (Object player : players) {
if (!api.isFloodgatePlayer(getUuidFromSource(player))) {
usernames.add(getUsernameFromSource(player));
}
}
break;
case ONLY_BEDROCK:
for (Object player : players) {
if (api.isFloodgatePlayer(getUuidFromSource(player))) {
usernames.add(getUsernameFromSource(player));
}
}
break;
default:
throw new IllegalStateException("Unknown PlayerType");
}
return usernames;
}
/**
*
* @param uuid
* @return
*/
public abstract Object getPlayerByUuid(@NonNull UUID uuid);
public Object getPlayerByUuid(@NonNull UUID uuid, PlayerType limitTo) {
return applyPlayerTypeFilter(getPlayerByUuid(uuid), limitTo, uuid);
}
public abstract Object getPlayerByUsername(@NonNull String username);
public Object getPlayerByUsername(@NonNull String username, PlayerType limitTo) {
return applyPlayerTypeFilter(getPlayerByUsername(username), limitTo, username);
}
protected Object applyPlayerTypeFilter(Object player, PlayerType filter, Object fallback) {
if (filter == ALL_PLAYERS || player instanceof String || player instanceof UUID) {
return player;
}
return (filter == ONLY_BEDROCK) == api.isFloodgatePlayer(getUuidFromSource(player))
? player
: fallback;
}
/**
* Checks if the given player has the given permission.
@@ -57,7 +145,7 @@ public interface CommandUtil {
* @param permission the permission to check
* @return true or false depending on if the player has the permission
*/
boolean hasPermission(Object player, String permission);
public abstract boolean hasPermission(Object player, String permission);
/**
* Get all online players with the given permission.
@@ -65,17 +153,15 @@ public interface CommandUtil {
* @param permission the permission to check
* @return a list of online players that have the given permission
*/
Collection<Object> getOnlinePlayersWithPermission(String permission);
/**
* Send a message to the specified target, no matter what platform Floodgate is running on.
*
* @param target the player that should receive the message
* @param message the command message
* @param locale the locale of the player
* @param args the arguments
*/
void sendMessage(Object target, String locale, TranslatableMessage message, Object... args);
public Collection<Object> getOnlinePlayersWithPermission(String permission) {
List<Object> players = new ArrayList<>();
for (Object player : getOnlinePlayers()) {
if (hasPermission(player, permission)) {
players.add(player);
}
}
return players;
}
/**
* Sends a raw message to the specified target, no matter what platform Floodgate is running
@@ -84,18 +170,19 @@ public interface CommandUtil {
* @param target the player that should receive the message
* @param message the message
*/
void sendMessage(Object target, String message);
public abstract void sendMessage(Object target, String message);
/**
* Same as {@link CommandUtil#sendMessage(Object, String, TranslatableMessage, Object...)}
* except it kicks the player using the given message as the kick reason.
* Kicks the given player using the given message as the kick reason.
*
* @param player the player that should be kicked
* @param message the command message
* @param locale the locale of the player
* @param args the arguments
*/
void kickPlayer(Object player, String locale, TranslatableMessage message, Object... args);
public abstract void kickPlayer(Object player, String message);
public String translateMessage(String locale, TranslatableMessage message, Object... args) {
return message.translateMessage(manager, locale, args);
}
/**
* Whitelist the given Bedrock player.
@@ -105,7 +192,7 @@ public interface CommandUtil {
* @return true if the player has been whitelisted, false if the player was already whitelisted.
* Defaults to false when this platform doesn't support whitelisting.
*/
default boolean whitelistPlayer(String xuid, String username) {
public boolean whitelistPlayer(String xuid, String username) {
UUID uuid = Utils.getJavaUuid(xuid);
return whitelistPlayer(uuid, username);
}
@@ -118,7 +205,7 @@ public interface CommandUtil {
* @return true if the player has been whitelisted, false if the player was already whitelisted.
* Defaults to false when this platform doesn't support whitelisting.
*/
default boolean whitelistPlayer(UUID uuid, String username) {
public boolean whitelistPlayer(UUID uuid, String username) {
return false;
}
@@ -130,7 +217,7 @@ public interface CommandUtil {
* @return true if the player has been removed from the whitelist, false if the player wasn't
* whitelisted. Defaults to false when this platform doesn't support whitelisting.
*/
default boolean removePlayerFromWhitelist(String xuid, String username) {
public boolean removePlayerFromWhitelist(String xuid, String username) {
UUID uuid = Utils.getJavaUuid(xuid);
return removePlayerFromWhitelist(uuid, username);
}
@@ -143,7 +230,7 @@ public interface CommandUtil {
* @return true if the player has been removed from the whitelist, false if the player wasn't
* whitelisted. Defaults to false when this platform doesn't support whitelisting.
*/
default boolean removePlayerFromWhitelist(UUID uuid, String username) {
public boolean removePlayerFromWhitelist(UUID uuid, String username) {
return false;
}
}

View File

@@ -28,8 +28,8 @@ package org.geysermc.floodgate.platform.command;
import org.geysermc.floodgate.util.LanguageManager;
/**
* TranslatableMessage is the interface for a message that can be translated.
* Messages are generally implemented using enums.
* TranslatableMessage is the interface for a message that can be translated. Messages are generally
* implemented using enums.
*/
public interface TranslatableMessage {
/**
@@ -52,7 +52,7 @@ public interface TranslatableMessage {
for (int i = 0; i < translateParts.length; i++) {
builder.append(manager.getString(translateParts[i], locale, args));
if (translateParts.length != i + 1) {
builder.append(" ");
builder.append(' ');
}
}
return builder.toString();

View File

@@ -25,37 +25,26 @@
package org.geysermc.floodgate.platform.util;
import java.util.Collection;
import org.geysermc.floodgate.platform.command.CommandUtil;
import org.geysermc.floodgate.platform.command.TranslatableMessage;
import lombok.RequiredArgsConstructor;
public interface PlatformUtils {
@RequiredArgsConstructor
public abstract class PlatformUtils {
/**
* Send a message to the specified player, no matter what platform Floodgate is running on.
*
* @param player the player to send the message to
* @param message the command message
* @param locale the locale of the player
* @param args the arguments
* Returns the authentication type used on the platform
*/
void sendMessage(Object player, String locale, TranslatableMessage message, Object... args);
public abstract AuthType authType();
/**
* Same as {@link CommandUtil#sendMessage(Object, String, TranslatableMessage, Object...)} except it
* kicks the player.
*
* @param player the player to send the message to
* @param message the command message
* @param locale the locale of the player
* @param args the arguments
* Returns the Minecraft version the server is based on (or the most recent supported version
* for proxy platforms)
*/
void kickPlayer(Object player, String locale, TranslatableMessage message, Object... args);
public abstract String minecraftVersion();
Collection<String> getOnlineUsernames(PlayerType limitTo);
public abstract String serverImplementationName();
enum PlayerType {
ALL_PLAYERS,
ONLY_BEDROCK,
ONLY_JAVA
public enum AuthType {
ONLINE,
PROXIED,
OFFLINE
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.platform.util;
public enum PlayerType {
ALL_PLAYERS,
ONLY_BEDROCK,
ONLY_JAVA
}

View File

@@ -35,7 +35,6 @@ import io.netty.util.AttributeKey;
import it.unimi.dsi.fastutil.Pair;
import it.unimi.dsi.fastutil.objects.ObjectObjectImmutablePair;
import java.net.InetSocketAddress;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import lombok.AccessLevel;
@@ -96,7 +95,7 @@ public final class FloodgateHandshakeHandler {
String floodgateData = null;
int dataVersion = -1;
StringBuilder hostnameBuilder = new StringBuilder();
StringBuilder builder = new StringBuilder();
for (String value : hostnameItems) {
int version = FloodgateCipher.version(value);
if (floodgateData == null && version != -1) {
@@ -104,10 +103,14 @@ public final class FloodgateHandshakeHandler {
dataVersion = version;
continue;
}
hostnameBuilder.append(value).append('\0');
if (builder.length() > 0) {
builder.append('\0');
}
builder.append(value);
}
// hostname now doesn't have Floodgate data anymore if it had
return new HostnameSeparationResult(floodgateData, dataVersion, hostnameBuilder.toString());
// the new hostname doesn't have Floodgate data anymore, if it had Floodgate data.
return new HostnameSeparationResult(floodgateData, dataVersion, builder.toString());
}
public CompletableFuture<HandshakeResult> handle(
@@ -218,8 +221,6 @@ public final class FloodgateHandshakeHandler {
bedrockData.getVerifyCode());
}
correctHostname(handshakeData);
FloodgatePlayer player = FloodgatePlayerImpl.from(bedrockData, handshakeData);
api.addPlayer(player);
@@ -247,30 +248,9 @@ public final class FloodgateHandshakeHandler {
bedrockData, configHolder.get(), null, hostname);
handshakeHandlers.callHandshakeHandlers(handshakeData);
if (bedrockData != null) {
correctHostname(handshakeData);
}
return new HandshakeResult(resultType, handshakeData, bedrockData, null);
}
private void correctHostname(HandshakeData handshakeData) {
BedrockData bedrockData = handshakeData.getBedrockData();
UUID correctUuid = handshakeData.getCorrectUniqueId();
// replace the ip and uuid with the Bedrock client IP and an uuid based of the xuid
String[] split = handshakeData.getHostname().split("\0");
if (split.length >= 3) {
if (logger.isDebug()) {
logger.info("Replacing hostname arg1 '{}' with '{}' and arg2 '{}' with '{}'",
split[1], bedrockData.getIp(), split[2], correctUuid.toString());
}
split[1] = bedrockData.getIp();
split[2] = correctUuid.toString();
}
handshakeData.setHostname(String.join("\0", split));
}
private CompletableFuture<Pair<BedrockData, LinkedPlayer>> fetchLinkedPlayer(BedrockData data) {
if (!api.getPlayerLink().isEnabled()) {
return CompletableFuture.completedFuture(new ObjectObjectImmutablePair<>(data, null));

View File

@@ -25,52 +25,80 @@
package org.geysermc.floodgate.player;
import java.util.Objects;
import java.util.UUID;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.audience.MessageType;
import net.kyori.adventure.identity.Identified;
import net.kyori.adventure.identity.Identity;
import net.kyori.adventure.text.Component;
import lombok.Getter;
import lombok.experimental.Accessors;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.floodgate.platform.command.CommandUtil;
import org.geysermc.floodgate.platform.command.TranslatableMessage;
public interface UserAudience extends Identified, Identity, Audience {
@Override
@NonNull UUID uuid();
@Getter @Accessors(fluent = true)
public class UserAudience {
private final @NonNull UUID uuid;
private final @NonNull String username;
private final @NonNull String locale;
private final @NonNull Object source;
private final @NonNull CommandUtil commandUtil;
@NonNull String username();
@NonNull String locale();
@NonNull Object source();
boolean hasPermission(@NonNull final String permission);
@Override
void sendMessage(final @NonNull Identity source,
final @NonNull Component message,
final @NonNull MessageType type);
void sendMessage(TranslatableMessage message, Object... args);
default void sendMessage(String message) {
sendMessage(Component.text(message));
public UserAudience(
@NonNull UUID uuid,
@NonNull String username,
@NonNull String locale,
@NonNull Object source,
@NonNull CommandUtil commandUtil) {
this.uuid = Objects.requireNonNull(uuid);
this.username = username;
this.locale = Objects.requireNonNull(locale);
this.source = Objects.requireNonNull(source);
this.commandUtil = Objects.requireNonNull(commandUtil);
}
void disconnect(@NonNull final Component reason);
void disconnect(TranslatableMessage message, Object... args);
@Override
default @NonNull Identity identity() {
return this;
public boolean hasPermission(@NonNull String permission) {
return commandUtil.hasPermission(source(), permission);
}
interface PlayerAudience extends UserAudience {
boolean online();
public void sendMessage(String message) {
commandUtil.sendMessage(source(), message);
}
interface ConsoleAudience extends UserAudience {
public void sendMessage(TranslatableMessage message, Object... args) {
sendMessage(translateMessage(message, args));
}
public void disconnect(@NonNull String reason) {
commandUtil.kickPlayer(source(), reason);
}
public void disconnect(TranslatableMessage message, Object... args) {
disconnect(translateMessage(message, args));
}
public String translateMessage(TranslatableMessage message, Object... args) {
return commandUtil.translateMessage(locale(), message, args);
}
@Getter @Accessors(fluent = true)
public static class PlayerAudience extends UserAudience {
private final boolean online;
public PlayerAudience(
@NonNull UUID uuid,
@NonNull String username,
@NonNull String locale,
@NonNull Object source,
@NonNull CommandUtil commandUtil,
boolean online) {
super(uuid, username, locale, source, commandUtil);
this.online = online;
}
}
@Getter @Accessors(fluent = true)
public static class ConsoleAudience extends UserAudience {
public ConsoleAudience(@NonNull Object source, @NonNull CommandUtil commandUtil) {
super(new UUID(0, 0), "CONSOLE", "en_us", source, commandUtil);
}
}
}

View File

@@ -23,23 +23,20 @@
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.util;
package org.geysermc.floodgate.player.audience;
public enum Permissions {
COMMAND_MAIN("floodgate.command.floodgate"),
COMMAND_LINK("floodgate.command.linkaccount"),
COMMAND_UNLINK("floodgate.command.unlinkaccount"),
COMMAND_WHITELIST("floodgate.command.fwhitelist"),
import java.util.UUID;
import lombok.Getter;
import lombok.experimental.Accessors;
import org.checkerframework.checker.nullness.qual.Nullable;
NEWS_RECEIVE("floodgate.news.receive");
@Getter @Accessors(fluent = true)
public final class ProfileAudience {
private final @Nullable UUID uuid;
private final @Nullable String username;
private final String permission;
Permissions(String permission) {
this.permission = permission;
}
public String get() {
return permission;
public ProfileAudience(@Nullable UUID uuid, @Nullable String username) {
this.uuid = uuid;
this.username = username;
}
}

View File

@@ -23,7 +23,7 @@
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.player;
package org.geysermc.floodgate.player.audience;
import cloud.commandframework.arguments.CommandArgument;
import cloud.commandframework.arguments.parser.ArgumentParseResult;
@@ -37,62 +37,58 @@ import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.floodgate.platform.command.CommandUtil;
import org.geysermc.floodgate.platform.util.PlayerType;
import org.geysermc.floodgate.player.UserAudience;
public final class UserAudienceArgument extends CommandArgument<UserAudience, UserAudience> {
private UserAudienceArgument(final @NonNull String name, final UserAudienceParser parser) {
super(true, name, parser, UserAudience.class);
public class ProfileAudienceArgument extends CommandArgument<UserAudience, ProfileAudience> {
private ProfileAudienceArgument(@NonNull String name, ProfileAudienceParser parser) {
super(true, name, parser, ProfileAudience.class);
}
public static UserAudienceArgument of(
final String name,
final boolean allowUuid,
final boolean allowOffline,
final PlayerType limitTo) {
return new UserAudienceArgument(name,
new UserAudienceParser(allowUuid, allowOffline, limitTo));
public static ProfileAudienceArgument of(
String name,
boolean allowUuid,
boolean allowOffline,
PlayerType limitTo) {
return new ProfileAudienceArgument(name,
new ProfileAudienceParser(allowUuid, allowOffline, limitTo));
}
public static UserAudienceArgument of(
final String name,
final boolean allowOffline,
final PlayerType limitTo) {
public static ProfileAudienceArgument of(
String name,
boolean allowOffline,
PlayerType limitTo) {
return of(name, false, allowOffline, limitTo);
}
public static UserAudienceArgument ofOnline(final String name, final PlayerType limitTo) {
public static ProfileAudienceArgument ofOnline(String name, PlayerType limitTo) {
return of(name, false, false, limitTo);
}
public static UserAudienceArgument ofOnline(final String name, final boolean allowUuid) {
public static ProfileAudienceArgument ofOnline(String name, boolean allowUuid) {
return of(name, allowUuid, false, PlayerType.ALL_PLAYERS);
}
public static CommandArgument<UserAudience, UserAudience> ofOnline(final String name) {
public static CommandArgument<UserAudience, ProfileAudience> ofOnline(String name) {
return of(name, false, false, PlayerType.ALL_PLAYERS);
}
public static UserAudienceArgument of(final String name, final boolean allowOffline) {
public static ProfileAudienceArgument of(String name, boolean allowOffline) {
return of(name, false, allowOffline, PlayerType.ALL_PLAYERS);
}
public enum PlayerType {
ALL_PLAYERS,
ONLY_BEDROCK,
ONLY_JAVA
}
@RequiredArgsConstructor
public static final class UserAudienceParser
implements ArgumentParser<UserAudience, UserAudience> {
public static final class ProfileAudienceParser
implements ArgumentParser<UserAudience, ProfileAudience> {
private final boolean allowUuid;
private final boolean allowOffline;
private final PlayerType limitTo;
@Override
public @NonNull ArgumentParseResult<UserAudience> parse(
final @NonNull CommandContext<@NonNull UserAudience> commandContext,
final @NonNull Queue<@NonNull String> inputQueue) {
public @NonNull ArgumentParseResult<ProfileAudience> parse(
@NonNull CommandContext<@NonNull UserAudience> commandContext,
@NonNull Queue<@NonNull String> inputQueue) {
CommandUtil commandUtil = commandContext.get("CommandUtil");
String input = inputQueue.poll();
@@ -111,7 +107,7 @@ public final class UserAudienceArgument extends CommandArgument<UserAudience, Us
StringBuilder builder = new StringBuilder(input);
while (!inputQueue.isEmpty()) {
String string = inputQueue.remove();
builder.append(" ").append(string);
builder.append(' ').append(string);
if (string.endsWith("\"")) {
break;
}
@@ -129,7 +125,7 @@ public final class UserAudienceArgument extends CommandArgument<UserAudience, Us
}
}
UserAudience userAudience;
ProfileAudience profileAudience;
if (input.length() > 16) {
// This must be a UUID.
@@ -146,46 +142,39 @@ public final class UserAudienceArgument extends CommandArgument<UserAudience, Us
try {
// We only want to make sure the UUID is valid here.
final UUID uuid = UUID.fromString(input);
userAudience = commandUtil.getAudienceByUuid(uuid);
if (userAudience == null && allowOffline) {
userAudience = commandUtil.getOfflineAudienceByUuid(uuid);
}
Object player = commandUtil.getPlayerByUuid(UUID.fromString(input), limitTo);
profileAudience = commandUtil.getProfileAudience(player, allowOffline);
} catch (final IllegalArgumentException ignored) {
return ArgumentParseResult.failure(
new InvalidPlayerIdentifierException("Invalid UUID '" + input + "'"));
}
} else {
// This is a username.
userAudience = commandUtil.getAudienceByUsername(input);
if (userAudience == null && allowOffline) {
userAudience = commandUtil.getOfflineAudienceByUsername(input);
}
Object player = commandUtil.getPlayerByUsername(input, limitTo);
profileAudience = commandUtil.getProfileAudience(player, allowOffline);
}
if (userAudience == null) {
if (profileAudience == null) {
return ArgumentParseResult.failure(
new InvalidPlayerIdentifierException("Invalid player '" + input + "'"));
}
return ArgumentParseResult.success(userAudience);
return ArgumentParseResult.success(profileAudience);
}
@Override
public @NonNull List<String> suggestions(
final @NonNull CommandContext<UserAudience> commandContext,
final @NonNull String input) {
final CommandUtil commandUtil = commandContext.get("CommandUtil");
final String trimmedInput = input.trim();
@NonNull CommandContext<UserAudience> commandContext,
@NonNull String input) {
CommandUtil commandUtil = commandContext.get("CommandUtil");
String trimmedInput = input.trim();
if (trimmedInput.isEmpty()) {
return ImmutableList.copyOf(commandUtil.getOnlineUsernames(limitTo));
}
final String lowercaseInput = input.toLowerCase(Locale.ROOT);
final ImmutableList.Builder<String> builder = ImmutableList.builder();
String lowercaseInput = input.toLowerCase(Locale.ROOT);
ImmutableList.Builder<String> builder = ImmutableList.builder();
for (final String player : commandUtil.getOnlineUsernames(limitTo)) {
if (player.toLowerCase(Locale.ROOT).startsWith(lowercaseInput)) {
@@ -205,7 +194,7 @@ public final class UserAudienceArgument extends CommandArgument<UserAudience, Us
public static final class InvalidPlayerIdentifierException extends IllegalArgumentException {
private static final long serialVersionUID = -6500019324607183855L;
public InvalidPlayerIdentifierException(final @NonNull String message) {
public InvalidPlayerIdentifierException(@NonNull String message) {
super(message);
}

View File

@@ -26,8 +26,10 @@
package org.geysermc.floodgate.util;
public final class Constants {
public static final String VERSION = "${floodgateVersion}";
public static final int BUILD_NUMBER = Integer.parseInt("${buildNumber}");
public static final String GIT_BRANCH = "${branch}";
public static final int METRICS_ID = 14649;
public static final char COLOR_CHAR = '§';

View File

@@ -0,0 +1,147 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.util;
import com.google.inject.Inject;
import java.util.Collections;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.inject.Named;
import org.bstats.MetricsBase;
import org.bstats.charts.DrilldownPie;
import org.bstats.charts.SimplePie;
import org.bstats.charts.SingleLineChart;
import org.bstats.json.JsonObjectBuilder;
import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.config.FloodgateConfig.MetricsConfig;
import org.geysermc.floodgate.platform.util.PlatformUtils;
public final class Metrics {
private final MetricsBase metricsBase;
@Inject
Metrics(FloodgateConfig config, PlatformUtils platformUtils, FloodgateApi api,
@Named("implementationName") String implementationName, FloodgateLogger logger) {
MetricsConfig metricsConfig = config.getMetrics();
metricsBase = new MetricsBase(
"server-implementation",
metricsConfig.getUuid(),
Constants.METRICS_ID,
metricsConfig.isEnabled(),
this::appendPlatformData,
jsonObjectBuilder -> { /* NOP */ },
null,
() -> true, // remove this if/when we add some form of reload support
logger::error,
logger::info,
Constants.DEBUG_MODE,
Constants.DEBUG_MODE,
Constants.DEBUG_MODE
);
metricsBase.addCustomChart(
new SingleLineChart("players", api::getPlayerCount)
);
metricsBase.addCustomChart(
new DrilldownPie("player_count", () -> {
int playerCount = api.getPlayerCount();
// 0 = 0 - 4, 9 = 5 - 9, etc.
int category = playerCount / 5 * 5;
String categoryName = category + " - " + (category + 4);
return Collections.singletonMap(
implementationName,
Collections.singletonMap(categoryName, 1)
);
})
);
metricsBase.addCustomChart(
new SimplePie("authentication",
() -> platformUtils.authType().name().toLowerCase(Locale.ROOT))
);
metricsBase.addCustomChart(
new SimplePie("floodgate_version", () -> Constants.VERSION)
);
metricsBase.addCustomChart(
new DrilldownPie("platform", () -> Collections.singletonMap(
implementationName,
Collections.singletonMap(platformUtils.serverImplementationName(), 1)
)));
metricsBase.addCustomChart(
new DrilldownPie("minecraft_version", () -> {
// e.g.: 1.16.5 => (Spigot, 1)
return Collections.singletonMap(
implementationName,
Collections.singletonMap(platformUtils.minecraftVersion(), 1)
);
})
);
// Source: Geyser
metricsBase.addCustomChart(new DrilldownPie("java_version", () -> {
Map<String, Map<String, Integer>> map = new HashMap<>();
String javaVersion = System.getProperty("java.version");
Map<String, Integer> entry = new HashMap<>();
entry.put(javaVersion, 1);
String majorVersion = javaVersion.split("\\.")[0];
String release;
int indexOf = javaVersion.lastIndexOf('.');
if (majorVersion.equals("1")) {
release = "Java " + javaVersion.substring(0, indexOf);
} else {
Matcher versionMatcher = Pattern.compile("\\d+").matcher(majorVersion);
if (versionMatcher.find()) {
majorVersion = versionMatcher.group(0);
}
release = "Java " + majorVersion;
}
map.put(release, entry);
return map;
}));
}
private void appendPlatformData(JsonObjectBuilder builder) {
builder.appendField("osName", System.getProperty("os.name"));
builder.appendField("osArch", System.getProperty("os.arch"));
builder.appendField("osVersion", System.getProperty("os.version"));
builder.appendField("coreCount", Runtime.getRuntime().availableProcessors());
}
}

View File

@@ -269,6 +269,11 @@ public final class ReflectionUtils {
return (T) getValue(instance, getField(instance.getClass(), fieldName));
}
@Nullable
public static <T> T castedStaticValue(Field field) {
return getCastedValue(null, field);
}
/**
* Set the value of a field. This method make the field accessible and then sets the value.<br>
* This method doesn't throw an exception when failed, but it'll log the error to the console.

View File

@@ -58,5 +58,9 @@ player-link:
# you have limited internet access.
enable-global-linking: true
metrics:
enabled: true
uuid: ${metrics.uuid}
# Do not change this
config-version: 2
config-version: 3