mirror of
https://github.com/GeyserMC/Geyser.git
synced 2024-08-14 23:57:35 +00:00
Merge branch 'master' into feature/1.21.20
This commit is contained in:
commit
402ea109a8
111 changed files with 2865 additions and 1953 deletions
|
@ -51,6 +51,9 @@ dependencies {
|
|||
// Adventure text serialization
|
||||
api(libs.bundles.adventure)
|
||||
|
||||
// command library
|
||||
api(libs.cloud.core)
|
||||
|
||||
api(libs.erosion.common) {
|
||||
isTransitive = false
|
||||
}
|
||||
|
|
|
@ -35,9 +35,7 @@ public final class Constants {
|
|||
public static final String NEWS_PROJECT_NAME = "geyser";
|
||||
|
||||
public static final String FLOODGATE_DOWNLOAD_LOCATION = "https://geysermc.org/download#floodgate";
|
||||
|
||||
public static final String GEYSER_DOWNLOAD_LOCATION = "https://geysermc.org/download";
|
||||
public static final String UPDATE_PERMISSION = "geyser.update";
|
||||
|
||||
@Deprecated
|
||||
static final String SAVED_REFRESH_TOKEN_FILE = "saved-refresh-tokens.json";
|
||||
|
|
|
@ -27,7 +27,7 @@ package org.geysermc.geyser;
|
|||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.geyser.command.GeyserCommandManager;
|
||||
import org.geysermc.geyser.command.CommandRegistry;
|
||||
import org.geysermc.geyser.configuration.GeyserConfiguration;
|
||||
import org.geysermc.geyser.dump.BootstrapDumpInfo;
|
||||
import org.geysermc.geyser.level.GeyserWorldManager;
|
||||
|
@ -82,11 +82,11 @@ public interface GeyserBootstrap {
|
|||
GeyserLogger getGeyserLogger();
|
||||
|
||||
/**
|
||||
* Returns the current CommandManager
|
||||
* Returns the current CommandRegistry
|
||||
*
|
||||
* @return The current CommandManager
|
||||
* @return The current CommandRegistry
|
||||
*/
|
||||
GeyserCommandManager getGeyserCommandManager();
|
||||
CommandRegistry getCommandRegistry();
|
||||
|
||||
/**
|
||||
* Returns the current PingPassthrough manager
|
||||
|
|
|
@ -62,13 +62,14 @@ import org.geysermc.geyser.api.event.lifecycle.GeyserPostInitializeEvent;
|
|||
import org.geysermc.geyser.api.event.lifecycle.GeyserPostReloadEvent;
|
||||
import org.geysermc.geyser.api.event.lifecycle.GeyserPreInitializeEvent;
|
||||
import org.geysermc.geyser.api.event.lifecycle.GeyserPreReloadEvent;
|
||||
import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent;
|
||||
import org.geysermc.geyser.api.event.lifecycle.GeyserShutdownEvent;
|
||||
import org.geysermc.geyser.api.network.AuthType;
|
||||
import org.geysermc.geyser.api.network.BedrockListener;
|
||||
import org.geysermc.geyser.api.network.RemoteServer;
|
||||
import org.geysermc.geyser.api.util.MinecraftVersion;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.command.GeyserCommandManager;
|
||||
import org.geysermc.geyser.command.CommandRegistry;
|
||||
import org.geysermc.geyser.configuration.GeyserConfiguration;
|
||||
import org.geysermc.geyser.entity.EntityDefinitions;
|
||||
import org.geysermc.geyser.erosion.UnixSocketClientListener;
|
||||
|
@ -128,7 +129,7 @@ import java.util.regex.Matcher;
|
|||
import java.util.regex.Pattern;
|
||||
|
||||
@Getter
|
||||
public class GeyserImpl implements GeyserApi {
|
||||
public class GeyserImpl implements GeyserApi, EventRegistrar {
|
||||
public static final ObjectMapper JSON_MAPPER = new ObjectMapper()
|
||||
.enable(JsonParser.Feature.IGNORE_UNDEFINED)
|
||||
.enable(JsonParser.Feature.ALLOW_COMMENTS)
|
||||
|
@ -231,9 +232,7 @@ public class GeyserImpl implements GeyserApi {
|
|||
logger.info(GeyserLocale.getLocaleStringLog("geyser.core.load", NAME, VERSION));
|
||||
logger.info("");
|
||||
if (IS_DEV) {
|
||||
// TODO cloud use language string
|
||||
//logger.info(GeyserLocale.getLocaleStringLog("geyser.core.dev_build", "https://discord.gg/geysermc"));
|
||||
logger.info("You are running a development build of Geyser! Please report any bugs you find on our Discord server: %s".formatted("https://discord.gg/geysermc"));
|
||||
logger.info(GeyserLocale.getLocaleStringLog("geyser.core.dev_build", "https://discord.gg/geysermc"));
|
||||
logger.info("");
|
||||
}
|
||||
logger.info("******************************************");
|
||||
|
@ -266,6 +265,9 @@ public class GeyserImpl implements GeyserApi {
|
|||
CompletableFuture.runAsync(AssetUtils::downloadAndRunClientJarTasks);
|
||||
});
|
||||
|
||||
// Register our general permissions when possible
|
||||
eventBus.subscribe(this, GeyserRegisterPermissionsEvent.class, Permissions::register);
|
||||
|
||||
startInstance();
|
||||
|
||||
GeyserConfiguration config = bootstrap.getGeyserConfig();
|
||||
|
@ -730,7 +732,6 @@ public class GeyserImpl implements GeyserApi {
|
|||
if (isEnabled) {
|
||||
this.disable();
|
||||
}
|
||||
this.commandManager().getCommands().clear();
|
||||
|
||||
// Disable extensions, fire the shutdown event
|
||||
this.eventBus.fire(new GeyserShutdownEvent(this.extensionManager, this.eventBus));
|
||||
|
@ -768,9 +769,12 @@ public class GeyserImpl implements GeyserApi {
|
|||
return this.extensionManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the current CommandRegistry in use. The instance may change over the lifecycle of the Geyser runtime.
|
||||
*/
|
||||
@NonNull
|
||||
public GeyserCommandManager commandManager() {
|
||||
return this.bootstrap.getGeyserCommandManager();
|
||||
public CommandRegistry commandRegistry() {
|
||||
return this.bootstrap.getCommandRegistry();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -30,6 +30,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
|||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.geyser.command.GeyserCommandSource;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public interface GeyserLogger extends GeyserCommandSource {
|
||||
|
||||
|
@ -129,6 +130,11 @@ public interface GeyserLogger extends GeyserCommandSource {
|
|||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
default @Nullable UUID playerUuid() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean hasPermission(String permission) {
|
||||
return true;
|
||||
|
|
63
core/src/main/java/org/geysermc/geyser/Permissions.java
Normal file
63
core/src/main/java/org/geysermc/geyser/Permissions.java
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright (c) 2024 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.geyser;
|
||||
|
||||
import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent;
|
||||
import org.geysermc.geyser.api.util.TriState;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Permissions related to Geyser
|
||||
*/
|
||||
public final class Permissions {
|
||||
private static final Map<String, TriState> PERMISSIONS = new HashMap<>();
|
||||
|
||||
public static final String CHECK_UPDATE = register("geyser.update");
|
||||
public static final String SERVER_SETTINGS = register("geyser.settings.server");
|
||||
public static final String SETTINGS_GAMERULES = register("geyser.settings.gamerules");
|
||||
|
||||
private Permissions() {
|
||||
//no
|
||||
}
|
||||
|
||||
private static String register(String permission) {
|
||||
return register(permission, TriState.NOT_SET);
|
||||
}
|
||||
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
private static String register(String permission, TriState permissionDefault) {
|
||||
PERMISSIONS.put(permission, permissionDefault);
|
||||
return permission;
|
||||
}
|
||||
|
||||
public static void register(GeyserRegisterPermissionsEvent event) {
|
||||
for (Map.Entry<String, TriState> permission : PERMISSIONS.entrySet()) {
|
||||
event.register(permission.getKey(), permission.getValue());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,302 @@
|
|||
/*
|
||||
* 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/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.command;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.command.Command;
|
||||
import org.geysermc.geyser.api.event.EventRegistrar;
|
||||
import org.geysermc.geyser.api.event.lifecycle.GeyserDefineCommandsEvent;
|
||||
import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent;
|
||||
import org.geysermc.geyser.api.extension.Extension;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.api.util.TriState;
|
||||
import org.geysermc.geyser.command.defaults.AdvancedTooltipsCommand;
|
||||
import org.geysermc.geyser.command.defaults.AdvancementsCommand;
|
||||
import org.geysermc.geyser.command.defaults.ConnectionTestCommand;
|
||||
import org.geysermc.geyser.command.defaults.DumpCommand;
|
||||
import org.geysermc.geyser.command.defaults.ExtensionsCommand;
|
||||
import org.geysermc.geyser.command.defaults.HelpCommand;
|
||||
import org.geysermc.geyser.command.defaults.ListCommand;
|
||||
import org.geysermc.geyser.command.defaults.OffhandCommand;
|
||||
import org.geysermc.geyser.command.defaults.PingCommand;
|
||||
import org.geysermc.geyser.command.defaults.ReloadCommand;
|
||||
import org.geysermc.geyser.command.defaults.SettingsCommand;
|
||||
import org.geysermc.geyser.command.defaults.StatisticsCommand;
|
||||
import org.geysermc.geyser.command.defaults.StopCommand;
|
||||
import org.geysermc.geyser.command.defaults.VersionCommand;
|
||||
import org.geysermc.geyser.event.type.GeyserDefineCommandsEventImpl;
|
||||
import org.geysermc.geyser.extension.command.GeyserExtensionCommand;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
import org.incendo.cloud.Command.Builder;
|
||||
import org.incendo.cloud.CommandManager;
|
||||
import org.incendo.cloud.execution.ExecutionCoordinator;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.geysermc.geyser.command.GeyserCommand.DEFAULT_ROOT_COMMAND;
|
||||
|
||||
/**
|
||||
* Registers all built-in and extension commands to the given Cloud CommandManager.
|
||||
* <p>
|
||||
* Fires {@link GeyserDefineCommandsEvent} upon construction.
|
||||
* <p>
|
||||
* Subscribes to {@link GeyserRegisterPermissionsEvent} upon construction.
|
||||
* A new instance of this class (that registers the same permissions) shouldn't be created until the previous
|
||||
* instance is unsubscribed from the event.
|
||||
*/
|
||||
public class CommandRegistry implements EventRegistrar {
|
||||
|
||||
private static final String GEYSER_ROOT_PERMISSION = "geyser.command";
|
||||
|
||||
protected final GeyserImpl geyser;
|
||||
private final CommandManager<GeyserCommandSource> cloud;
|
||||
private final boolean applyRootPermission;
|
||||
|
||||
/**
|
||||
* Map of Geyser subcommands to their Commands
|
||||
*/
|
||||
private final Map<String, GeyserCommand> commands = new Object2ObjectOpenHashMap<>(13);
|
||||
|
||||
/**
|
||||
* Map of Extensions to maps of their subcommands
|
||||
*/
|
||||
private final Map<Extension, Map<String, GeyserCommand>> extensionCommands = new Object2ObjectOpenHashMap<>(0);
|
||||
|
||||
/**
|
||||
* Map of root commands (that are for extensions) to Extensions
|
||||
*/
|
||||
private final Map<String, Extension> extensionRootCommands = new Object2ObjectOpenHashMap<>(0);
|
||||
|
||||
/**
|
||||
* Map containing only permissions that have been registered with a default value
|
||||
*/
|
||||
protected final Map<String, TriState> permissionDefaults = new Object2ObjectOpenHashMap<>(13);
|
||||
|
||||
/**
|
||||
* Creates a new CommandRegistry. Does apply a root permission. If undesired, use the other constructor.
|
||||
*/
|
||||
public CommandRegistry(GeyserImpl geyser, CommandManager<GeyserCommandSource> cloud) {
|
||||
this(geyser, cloud, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new CommandRegistry
|
||||
*
|
||||
* @param geyser the Geyser instance
|
||||
* @param cloud the cloud command manager to register commands to
|
||||
* @param applyRootPermission true if this registry should apply a permission to Geyser and Extension root commands.
|
||||
* This currently exists because we want to retain the root command permission for Spigot,
|
||||
* but don't want to add it yet to platforms like Velocity where we cannot natively
|
||||
* specify a permission default. Doing so will break setups as players would suddenly not
|
||||
* have the required permission to execute any Geyser commands.
|
||||
*/
|
||||
public CommandRegistry(GeyserImpl geyser, CommandManager<GeyserCommandSource> cloud, boolean applyRootPermission) {
|
||||
this.geyser = geyser;
|
||||
this.cloud = cloud;
|
||||
this.applyRootPermission = applyRootPermission;
|
||||
|
||||
// register our custom exception handlers
|
||||
ExceptionHandlers.register(cloud);
|
||||
|
||||
// begin command registration
|
||||
HelpCommand help = new HelpCommand(DEFAULT_ROOT_COMMAND, "help", "geyser.commands.help.desc", "geyser.command.help", this.commands);
|
||||
registerBuiltInCommand(help);
|
||||
buildRootCommand(GEYSER_ROOT_PERMISSION, help); // build root and delegate to help
|
||||
|
||||
registerBuiltInCommand(new ListCommand(geyser, "list", "geyser.commands.list.desc", "geyser.command.list"));
|
||||
registerBuiltInCommand(new ReloadCommand(geyser, "reload", "geyser.commands.reload.desc", "geyser.command.reload"));
|
||||
registerBuiltInCommand(new OffhandCommand("offhand", "geyser.commands.offhand.desc", "geyser.command.offhand"));
|
||||
registerBuiltInCommand(new DumpCommand(geyser, "dump", "geyser.commands.dump.desc", "geyser.command.dump"));
|
||||
registerBuiltInCommand(new VersionCommand(geyser, "version", "geyser.commands.version.desc", "geyser.command.version"));
|
||||
registerBuiltInCommand(new SettingsCommand("settings", "geyser.commands.settings.desc", "geyser.command.settings"));
|
||||
registerBuiltInCommand(new StatisticsCommand("statistics", "geyser.commands.statistics.desc", "geyser.command.statistics"));
|
||||
registerBuiltInCommand(new AdvancementsCommand("advancements", "geyser.commands.advancements.desc", "geyser.command.advancements"));
|
||||
registerBuiltInCommand(new AdvancedTooltipsCommand("tooltips", "geyser.commands.advancedtooltips.desc", "geyser.command.tooltips"));
|
||||
registerBuiltInCommand(new ConnectionTestCommand(geyser, "connectiontest", "geyser.commands.connectiontest.desc", "geyser.command.connectiontest"));
|
||||
registerBuiltInCommand(new PingCommand("ping", "geyser.commands.ping.desc", "geyser.command.ping"));
|
||||
if (this.geyser.getPlatformType() == PlatformType.STANDALONE) {
|
||||
registerBuiltInCommand(new StopCommand(geyser, "stop", "geyser.commands.stop.desc", "geyser.command.stop"));
|
||||
}
|
||||
|
||||
if (!this.geyser.extensionManager().extensions().isEmpty()) {
|
||||
registerBuiltInCommand(new ExtensionsCommand(this.geyser, "extensions", "geyser.commands.extensions.desc", "geyser.command.extensions"));
|
||||
}
|
||||
|
||||
GeyserDefineCommandsEvent defineCommandsEvent = new GeyserDefineCommandsEventImpl(this.commands) {
|
||||
|
||||
@Override
|
||||
public void register(@NonNull Command command) {
|
||||
if (!(command instanceof GeyserExtensionCommand extensionCommand)) {
|
||||
throw new IllegalArgumentException("Expected GeyserExtensionCommand as part of command registration but got " + command + "! Did you use the Command builder properly?");
|
||||
}
|
||||
|
||||
registerExtensionCommand(extensionCommand.extension(), extensionCommand);
|
||||
}
|
||||
};
|
||||
this.geyser.eventBus().fire(defineCommandsEvent);
|
||||
|
||||
// Stuff that needs to be done on a per-extension basis
|
||||
for (Map.Entry<Extension, Map<String, GeyserCommand>> entry : this.extensionCommands.entrySet()) {
|
||||
Extension extension = entry.getKey();
|
||||
|
||||
// Register this extension's root command
|
||||
extensionRootCommands.put(extension.rootCommand(), extension);
|
||||
|
||||
// Register help commands for all extensions with commands
|
||||
String id = extension.description().id();
|
||||
HelpCommand extensionHelp = new HelpCommand(
|
||||
extension.rootCommand(),
|
||||
"help",
|
||||
"geyser.commands.exthelp.desc",
|
||||
"geyser.command.exthelp." + id,
|
||||
entry.getValue()); // commands it provides help for
|
||||
|
||||
registerExtensionCommand(extension, extensionHelp);
|
||||
buildRootCommand("geyser.extension." + id + ".command", extensionHelp);
|
||||
}
|
||||
|
||||
// Wait for the right moment (depends on the platform) to register permissions.
|
||||
geyser.eventBus().subscribe(this, GeyserRegisterPermissionsEvent.class, this::onRegisterPermissions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return an immutable view of the root commands registered to this command registry
|
||||
*/
|
||||
@NonNull
|
||||
public Collection<String> rootCommands() {
|
||||
return cloud.rootCommands();
|
||||
}
|
||||
|
||||
/**
|
||||
* For internal Geyser commands
|
||||
*/
|
||||
private void registerBuiltInCommand(GeyserCommand command) {
|
||||
register(command, this.commands);
|
||||
}
|
||||
|
||||
private void registerExtensionCommand(@NonNull Extension extension, @NonNull GeyserCommand command) {
|
||||
register(command, this.extensionCommands.computeIfAbsent(extension, e -> new HashMap<>()));
|
||||
}
|
||||
|
||||
protected void register(GeyserCommand command, Map<String, GeyserCommand> commands) {
|
||||
String root = command.rootCommand();
|
||||
String name = command.name();
|
||||
if (commands.containsKey(name)) {
|
||||
throw new IllegalArgumentException("Command with root=%s, name=%s already registered".formatted(root, name));
|
||||
}
|
||||
|
||||
command.register(cloud);
|
||||
commands.put(name, command);
|
||||
geyser.getLogger().debug(GeyserLocale.getLocaleStringLog("geyser.commands.registered", root + " " + name));
|
||||
|
||||
for (String alias : command.aliases()) {
|
||||
commands.put(alias, command);
|
||||
}
|
||||
|
||||
String permission = command.permission();
|
||||
TriState defaultValue = command.permissionDefault();
|
||||
if (!permission.isBlank() && defaultValue != null) {
|
||||
|
||||
TriState existingDefault = permissionDefaults.get(permission);
|
||||
// Extensions might be using the same permission for two different commands
|
||||
if (existingDefault != null && existingDefault != defaultValue) {
|
||||
geyser.getLogger().debug("Overriding permission default %s:%s with %s".formatted(permission, existingDefault, defaultValue));
|
||||
}
|
||||
|
||||
permissionDefaults.put(permission, defaultValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a root command to cloud that delegates to the given help command.
|
||||
* The name of this root command is the root of the given help command.
|
||||
*
|
||||
* @param permission the permission of the root command. currently, it may or may not be
|
||||
* applied depending on the platform. see below.
|
||||
* @param help the help command to delegate to
|
||||
*/
|
||||
private void buildRootCommand(String permission, HelpCommand help) {
|
||||
Builder<GeyserCommandSource> builder = cloud.commandBuilder(help.rootCommand());
|
||||
|
||||
if (applyRootPermission) {
|
||||
builder = builder.permission(permission);
|
||||
permissionDefaults.put(permission, TriState.TRUE);
|
||||
}
|
||||
|
||||
cloud.command(builder.handler(context -> {
|
||||
GeyserCommandSource source = context.sender();
|
||||
if (!source.hasPermission(help.permission())) {
|
||||
// delegate if possible - otherwise we have nothing else to offer the user.
|
||||
source.sendLocaleString(ExceptionHandlers.PERMISSION_FAIL_LANG_KEY);
|
||||
return;
|
||||
}
|
||||
help.execute(source);
|
||||
}));
|
||||
}
|
||||
|
||||
protected void onRegisterPermissions(GeyserRegisterPermissionsEvent event) {
|
||||
for (Map.Entry<String, TriState> permission : permissionDefaults.entrySet()) {
|
||||
event.register(permission.getKey(), permission.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasPermission(GeyserCommandSource source, String permission) {
|
||||
// Handle blank permissions ourselves, as cloud only handles empty ones
|
||||
return permission.isBlank() || cloud.hasPermission(source, permission);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the description of the given command
|
||||
*
|
||||
* @param command the root command node
|
||||
* @param locale the ideal locale that the description should be in
|
||||
* @return a description if found, otherwise an empty string. The locale is not guaranteed.
|
||||
*/
|
||||
@NonNull
|
||||
public String description(@NonNull String command, @NonNull String locale) {
|
||||
if (command.equals(DEFAULT_ROOT_COMMAND)) {
|
||||
return GeyserLocale.getPlayerLocaleString("geyser.command.root.geyser", locale);
|
||||
}
|
||||
|
||||
Extension extension = extensionRootCommands.get(command);
|
||||
if (extension != null) {
|
||||
return GeyserLocale.getPlayerLocaleString("geyser.command.root.extension", locale, extension.name());
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches a command into cloud and handles any thrown exceptions.
|
||||
* This method may or may not be blocking, depending on the {@link ExecutionCoordinator} in use by cloud.
|
||||
*/
|
||||
public void runCommand(@NonNull GeyserCommandSource source, @NonNull String command) {
|
||||
cloud.commandExecutor().executeCommand(source, command);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2023 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.geyser.command;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.GeyserLogger;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.incendo.cloud.SenderMapper;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Converts {@link GeyserCommandSource}s to the server's command sender type (and back) in a lenient manner.
|
||||
*
|
||||
* @param senderType class of the server command sender type
|
||||
* @param playerLookup function for looking up a player command sender by UUID
|
||||
* @param consoleProvider supplier of the console command sender
|
||||
* @param commandSourceLookup supplier of the platform implementation of the {@link GeyserCommandSource}
|
||||
* @param <S> server command sender type
|
||||
*/
|
||||
public record CommandSourceConverter<S>(Class<S> senderType,
|
||||
Function<UUID, S> playerLookup,
|
||||
Supplier<S> consoleProvider,
|
||||
Function<S, GeyserCommandSource> commandSourceLookup
|
||||
) implements SenderMapper<S, GeyserCommandSource> {
|
||||
|
||||
/**
|
||||
* Creates a new CommandSourceConverter for a server platform
|
||||
* in which the player type is not a command sender type, and must be mapped.
|
||||
*
|
||||
* @param senderType class of the command sender type
|
||||
* @param playerLookup function for looking up a player by UUID
|
||||
* @param senderLookup function for converting a player to a command sender
|
||||
* @param consoleProvider supplier of the console command sender
|
||||
* @param commandSourceLookup supplier of the platform implementation of {@link GeyserCommandSource}
|
||||
* @return a new CommandSourceConverter
|
||||
* @param <P> server player type
|
||||
* @param <S> server command sender type
|
||||
*/
|
||||
public static <P, S> CommandSourceConverter<S> layered(Class<S> senderType,
|
||||
Function<UUID, P> playerLookup,
|
||||
Function<P, S> senderLookup,
|
||||
Supplier<S> consoleProvider,
|
||||
Function<S, GeyserCommandSource> commandSourceLookup) {
|
||||
Function<UUID, S> lookup = uuid -> {
|
||||
P player = playerLookup.apply(uuid);
|
||||
if (player == null) {
|
||||
return null;
|
||||
}
|
||||
return senderLookup.apply(player);
|
||||
};
|
||||
return new CommandSourceConverter<>(senderType, lookup, consoleProvider, commandSourceLookup);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull GeyserCommandSource map(@NonNull S base) {
|
||||
return commandSourceLookup.apply(base);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull S reverse(GeyserCommandSource source) throws IllegalArgumentException {
|
||||
Object handle = source.handle();
|
||||
if (senderType.isInstance(handle)) {
|
||||
return senderType.cast(handle); // one of the server platform implementations
|
||||
}
|
||||
|
||||
if (source.isConsole()) {
|
||||
return consoleProvider.get(); // one of the loggers
|
||||
}
|
||||
|
||||
if (!(source instanceof GeyserSession)) {
|
||||
GeyserLogger logger = GeyserImpl.getInstance().getLogger();
|
||||
if (logger.isDebug()) {
|
||||
logger.debug("Falling back to UUID for command sender lookup for a command source that is not a GeyserSession: " + source);
|
||||
Thread.dumpStack();
|
||||
}
|
||||
}
|
||||
|
||||
// Ideally lookup should only be necessary for GeyserSession
|
||||
UUID uuid = source.playerUuid();
|
||||
if (uuid != null) {
|
||||
return playerLookup.apply(uuid);
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("failed to find sender for name=%s, uuid=%s".formatted(source.name(), source.playerUuid()));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2024 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.geyser.command;
|
||||
|
||||
import io.leangen.geantyref.GenericTypeReflector;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.GeyserLogger;
|
||||
import org.geysermc.geyser.text.ChatColor;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
import org.geysermc.geyser.text.MinecraftLocale;
|
||||
import org.incendo.cloud.CommandManager;
|
||||
import org.incendo.cloud.exception.ArgumentParseException;
|
||||
import org.incendo.cloud.exception.CommandExecutionException;
|
||||
import org.incendo.cloud.exception.InvalidCommandSenderException;
|
||||
import org.incendo.cloud.exception.InvalidSyntaxException;
|
||||
import org.incendo.cloud.exception.NoPermissionException;
|
||||
import org.incendo.cloud.exception.NoSuchCommandException;
|
||||
import org.incendo.cloud.exception.handling.ExceptionController;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
/**
|
||||
* Geyser's exception handlers for command execution with Cloud.
|
||||
* Overrides Cloud's defaults so that messages can be customized to our liking: localization, etc.
|
||||
*/
|
||||
final class ExceptionHandlers {
|
||||
|
||||
final static String PERMISSION_FAIL_LANG_KEY = "geyser.command.permission_fail";
|
||||
|
||||
private final ExceptionController<GeyserCommandSource> controller;
|
||||
|
||||
private ExceptionHandlers(ExceptionController<GeyserCommandSource> controller) {
|
||||
this.controller = controller;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the existing handlers that are registered to the given command manager, and repopulates them.
|
||||
*
|
||||
* @param manager the manager whose exception handlers will be modified
|
||||
*/
|
||||
static void register(CommandManager<GeyserCommandSource> manager) {
|
||||
new ExceptionHandlers(manager.exceptionController()).register();
|
||||
}
|
||||
|
||||
private void register() {
|
||||
// Yeet the default exception handlers that cloud provides so that we can perform localization.
|
||||
controller.clearHandlers();
|
||||
|
||||
registerExceptionHandler(InvalidSyntaxException.class,
|
||||
(src, e) -> src.sendLocaleString("geyser.command.invalid_syntax", e.correctSyntax()));
|
||||
|
||||
registerExceptionHandler(InvalidCommandSenderException.class, (src, e) -> {
|
||||
// We currently don't use cloud sender type requirements anywhere.
|
||||
// This can be implemented better in the future if necessary.
|
||||
Type type = e.requiredSenderTypes().iterator().next(); // just grab the first
|
||||
String typeString = GenericTypeReflector.getTypeName(type);
|
||||
src.sendLocaleString("geyser.command.invalid_sender", e.commandSender().getClass().getSimpleName(), typeString);
|
||||
});
|
||||
|
||||
registerExceptionHandler(NoPermissionException.class, ExceptionHandlers::handleNoPermission);
|
||||
|
||||
registerExceptionHandler(NoSuchCommandException.class,
|
||||
(src, e) -> src.sendLocaleString("geyser.command.not_found"));
|
||||
|
||||
registerExceptionHandler(ArgumentParseException.class,
|
||||
(src, e) -> src.sendLocaleString("geyser.command.invalid_argument", e.getCause().getMessage()));
|
||||
|
||||
registerExceptionHandler(CommandExecutionException.class,
|
||||
(src, e) -> handleUnexpectedThrowable(src, e.getCause()));
|
||||
|
||||
registerExceptionHandler(Throwable.class,
|
||||
(src, e) -> handleUnexpectedThrowable(src, e.getCause()));
|
||||
}
|
||||
|
||||
private <E extends Throwable> void registerExceptionHandler(Class<E> type, BiConsumer<GeyserCommandSource, E> handler) {
|
||||
controller.registerHandler(type, context -> handler.accept(context.context().sender(), context.exception()));
|
||||
}
|
||||
|
||||
private static void handleNoPermission(GeyserCommandSource source, NoPermissionException exception) {
|
||||
// custom handling if the source can't use the command because of additional requirements
|
||||
if (exception.permissionResult() instanceof GeyserPermission.Result result) {
|
||||
if (result.meta() == GeyserPermission.Result.Meta.NOT_BEDROCK) {
|
||||
source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.command.bedrock_only", source.locale()));
|
||||
return;
|
||||
}
|
||||
if (result.meta() == GeyserPermission.Result.Meta.NOT_PLAYER) {
|
||||
source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.command.player_only", source.locale()));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
GeyserLogger logger = GeyserImpl.getInstance().getLogger();
|
||||
if (logger.isDebug()) {
|
||||
logger.debug("Expected a GeyserPermission.Result for %s but instead got %s from %s".formatted(exception.currentChain(), exception.permissionResult(), exception.missingPermission()));
|
||||
}
|
||||
}
|
||||
|
||||
// Result.NO_PERMISSION or generic permission failure
|
||||
source.sendLocaleString(PERMISSION_FAIL_LANG_KEY);
|
||||
}
|
||||
|
||||
private static void handleUnexpectedThrowable(GeyserCommandSource source, Throwable throwable) {
|
||||
source.sendMessage(MinecraftLocale.getLocaleString("command.failed", source.locale())); // java edition translation key
|
||||
GeyserImpl.getInstance().getLogger().error("Exception while executing command handler", throwable);
|
||||
}
|
||||
}
|
|
@ -25,65 +25,187 @@
|
|||
|
||||
package org.geysermc.geyser.command;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.geyser.api.command.Command;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent;
|
||||
import org.geysermc.geyser.api.util.TriState;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
import org.incendo.cloud.Command;
|
||||
import org.incendo.cloud.CommandManager;
|
||||
import org.incendo.cloud.context.CommandContext;
|
||||
import org.incendo.cloud.description.CommandDescription;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@Accessors(fluent = true)
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public abstract class GeyserCommand implements Command {
|
||||
public abstract class GeyserCommand implements org.geysermc.geyser.api.command.Command {
|
||||
public static final String DEFAULT_ROOT_COMMAND = "geyser";
|
||||
|
||||
/**
|
||||
* The second literal of the command. Note: the first literal is {@link #rootCommand()}.
|
||||
*/
|
||||
@NonNull
|
||||
private final String name;
|
||||
|
||||
protected final String name;
|
||||
/**
|
||||
* The description of the command - will attempt to be translated.
|
||||
*/
|
||||
protected final String description;
|
||||
protected final String permission;
|
||||
|
||||
private List<String> aliases = Collections.emptyList();
|
||||
|
||||
public abstract void execute(@Nullable GeyserSession session, GeyserCommandSource sender, String[] args);
|
||||
@NonNull
|
||||
private final String description;
|
||||
|
||||
/**
|
||||
* If false, hides the command from being shown on the Geyser Standalone GUI.
|
||||
*
|
||||
* @return true if the command can be run on the server console
|
||||
*/
|
||||
@Override
|
||||
public boolean isExecutableOnConsole() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used in the GUI to know what subcommands can be run
|
||||
*
|
||||
* @return a list of all possible subcommands, or empty if none.
|
||||
* The permission node required to run the command, or blank if not required.
|
||||
*/
|
||||
@NonNull
|
||||
@Override
|
||||
public List<String> subCommands() {
|
||||
return Collections.emptyList();
|
||||
private final String permission;
|
||||
|
||||
/**
|
||||
* The default value of the permission node.
|
||||
* A null value indicates that the permission node should not be registered whatsoever.
|
||||
* See {@link GeyserRegisterPermissionsEvent#register(String, TriState)} for TriState meanings.
|
||||
*/
|
||||
@Nullable
|
||||
private final TriState permissionDefault;
|
||||
|
||||
/**
|
||||
* True if this command can be executed by players
|
||||
*/
|
||||
private final boolean playerOnly;
|
||||
|
||||
/**
|
||||
* True if this command can only be run by bedrock players
|
||||
*/
|
||||
private final boolean bedrockOnly;
|
||||
|
||||
/**
|
||||
* The aliases of the command {@link #name}. This should not be modified after construction.
|
||||
*/
|
||||
protected List<String> aliases = Collections.emptyList();
|
||||
|
||||
public GeyserCommand(@NonNull String name, @NonNull String description,
|
||||
@NonNull String permission, @Nullable TriState permissionDefault,
|
||||
boolean playerOnly, boolean bedrockOnly) {
|
||||
|
||||
if (name.isBlank()) {
|
||||
throw new IllegalArgumentException("Command cannot be null or blank!");
|
||||
}
|
||||
if (permission.isBlank()) {
|
||||
// Cloud treats empty permissions as available to everyone, but not blank permissions.
|
||||
// When registering commands, we must convert ALL whitespace permissions into empty ones,
|
||||
// because we cannot override permission checks that Cloud itself performs
|
||||
permission = "";
|
||||
permissionDefault = null;
|
||||
}
|
||||
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.permission = permission;
|
||||
this.permissionDefault = permissionDefault;
|
||||
|
||||
if (bedrockOnly && !playerOnly) {
|
||||
throw new IllegalArgumentException("Command cannot be bedrockOnly if it is not playerOnly");
|
||||
}
|
||||
|
||||
this.playerOnly = playerOnly;
|
||||
this.bedrockOnly = bedrockOnly;
|
||||
}
|
||||
|
||||
public void setAliases(List<String> aliases) {
|
||||
this.aliases = aliases;
|
||||
public GeyserCommand(@NonNull String name, @NonNull String description, @NonNull String permission, @Nullable TriState permissionDefault) {
|
||||
this(name, description, permission, permissionDefault, false, false);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public final String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public final String description() {
|
||||
return description;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public final String permission() {
|
||||
return permission;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public final TriState permissionDefault() {
|
||||
return permissionDefault;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean isPlayerOnly() {
|
||||
return playerOnly;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean isBedrockOnly() {
|
||||
return bedrockOnly;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public final List<String> aliases() {
|
||||
return Collections.unmodifiableList(aliases);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for permission defaults on server implementations.
|
||||
*
|
||||
* @return if this command is designated to be used only by server operators.
|
||||
* @return the first (literal) argument of this command, which comes before {@link #name()}.
|
||||
*/
|
||||
@Override
|
||||
public boolean isSuggestedOpOnly() {
|
||||
return false;
|
||||
public String rootCommand() {
|
||||
return DEFAULT_ROOT_COMMAND;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link org.incendo.cloud.permission.Permission} that handles {@link #isBedrockOnly()}, {@link #isPlayerOnly()}, and {@link #permission()}.
|
||||
*
|
||||
* @param manager the manager to be used for permission node checking
|
||||
* @return a permission that will properly restrict usage of this command
|
||||
*/
|
||||
public final GeyserPermission commandPermission(CommandManager<GeyserCommandSource> manager) {
|
||||
return new GeyserPermission(bedrockOnly, playerOnly, permission, manager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new command builder with {@link #rootCommand()}, {@link #name()}, and {@link #aliases()} built on it.
|
||||
* A permission predicate that takes into account {@link #permission()}, {@link #isBedrockOnly()}, and {@link #isPlayerOnly()}
|
||||
* is applied. The Applicable from {@link #meta()} is also applied to the builder.
|
||||
*/
|
||||
@Contract(value = "_ -> new", pure = true)
|
||||
public final Command.Builder<GeyserCommandSource> baseBuilder(CommandManager<GeyserCommandSource> manager) {
|
||||
return manager.commandBuilder(rootCommand())
|
||||
.literal(name, aliases.toArray(new String[0]))
|
||||
.permission(commandPermission(manager))
|
||||
.apply(meta());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return an Applicable that applies this command's description
|
||||
*/
|
||||
protected Command.Builder.Applicable<GeyserCommandSource> meta() {
|
||||
return builder -> builder
|
||||
.commandDescription(CommandDescription.commandDescription(GeyserLocale.getLocaleStringLog(description))); // used in cloud-bukkit impl
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers this command to the given command manager.
|
||||
* This method may be overridden to register more than one command.
|
||||
* <p>
|
||||
* The default implementation is that {@link #baseBuilder(CommandManager)} with {@link #execute(CommandContext)}
|
||||
* applied as the handler is registered to the manager.
|
||||
*/
|
||||
public void register(CommandManager<GeyserCommandSource> manager) {
|
||||
manager.command(baseBuilder(manager).handler(this::execute));
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes this command
|
||||
* @param context the context with which this command should be executed
|
||||
*/
|
||||
public abstract void execute(CommandContext<GeyserCommandSource> context);
|
||||
}
|
||||
|
|
|
@ -1,98 +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/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.command;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.command.Command;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Represents helper functions for listening to {@code /geyser} or {@code /geyserext} commands.
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
public class GeyserCommandExecutor {
|
||||
|
||||
protected final GeyserImpl geyser;
|
||||
private final Map<String, Command> commands;
|
||||
|
||||
public GeyserCommand getCommand(String label) {
|
||||
return (GeyserCommand) commands.get(label);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public GeyserSession getGeyserSession(GeyserCommandSource sender) {
|
||||
if (sender.isConsole()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (GeyserSession session : geyser.getSessionManager().getSessions().values()) {
|
||||
if (sender.name().equals(session.getPlayerEntity().getUsername())) {
|
||||
return session;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine which subcommands to suggest in the tab complete for the main /geyser command by a given command sender.
|
||||
*
|
||||
* @param sender The command sender to receive the tab complete suggestions.
|
||||
* If the command sender is a bedrock player, an empty list will be returned as bedrock players do not get command argument suggestions.
|
||||
* If the command sender is not a bedrock player, bedrock commands will not be shown.
|
||||
* If the command sender does not have the permission for a given command, the command will not be shown.
|
||||
* @return A list of command names to include in the tab complete
|
||||
*/
|
||||
public List<String> tabComplete(GeyserCommandSource sender) {
|
||||
if (getGeyserSession(sender) != null) {
|
||||
// Bedrock doesn't get tab completions or argument suggestions
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<String> availableCommands = new ArrayList<>();
|
||||
|
||||
// Only show commands they have permission to use
|
||||
for (Map.Entry<String, Command> entry : commands.entrySet()) {
|
||||
Command geyserCommand = entry.getValue();
|
||||
if (sender.hasPermission(geyserCommand.permission())) {
|
||||
if (geyserCommand.isBedrockOnly()) {
|
||||
// Don't show commands the JE player can't run
|
||||
continue;
|
||||
}
|
||||
|
||||
availableCommands.add(entry.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
return availableCommands;
|
||||
}
|
||||
}
|
|
@ -1,330 +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/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.command;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.command.Command;
|
||||
import org.geysermc.geyser.api.command.CommandExecutor;
|
||||
import org.geysermc.geyser.api.command.CommandSource;
|
||||
import org.geysermc.geyser.api.event.lifecycle.GeyserDefineCommandsEvent;
|
||||
import org.geysermc.geyser.api.extension.Extension;
|
||||
import org.geysermc.geyser.command.defaults.AdvancedTooltipsCommand;
|
||||
import org.geysermc.geyser.command.defaults.AdvancementsCommand;
|
||||
import org.geysermc.geyser.command.defaults.ConnectionTestCommand;
|
||||
import org.geysermc.geyser.command.defaults.DumpCommand;
|
||||
import org.geysermc.geyser.command.defaults.ExtensionsCommand;
|
||||
import org.geysermc.geyser.command.defaults.HelpCommand;
|
||||
import org.geysermc.geyser.command.defaults.ListCommand;
|
||||
import org.geysermc.geyser.command.defaults.OffhandCommand;
|
||||
import org.geysermc.geyser.command.defaults.ReloadCommand;
|
||||
import org.geysermc.geyser.command.defaults.SettingsCommand;
|
||||
import org.geysermc.geyser.command.defaults.StatisticsCommand;
|
||||
import org.geysermc.geyser.command.defaults.StopCommand;
|
||||
import org.geysermc.geyser.command.defaults.VersionCommand;
|
||||
import org.geysermc.geyser.event.type.GeyserDefineCommandsEventImpl;
|
||||
import org.geysermc.geyser.extension.command.GeyserExtensionCommand;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class GeyserCommandManager {
|
||||
|
||||
@Getter
|
||||
private final Map<String, Command> commands = new Object2ObjectOpenHashMap<>(12);
|
||||
private final Map<Extension, Map<String, Command>> extensionCommands = new Object2ObjectOpenHashMap<>(0);
|
||||
|
||||
private final GeyserImpl geyser;
|
||||
|
||||
public void init() {
|
||||
registerBuiltInCommand(new HelpCommand(geyser, "help", "geyser.commands.help.desc", "geyser.command.help", "geyser", this.commands));
|
||||
registerBuiltInCommand(new ListCommand(geyser, "list", "geyser.commands.list.desc", "geyser.command.list"));
|
||||
registerBuiltInCommand(new ReloadCommand(geyser, "reload", "geyser.commands.reload.desc", "geyser.command.reload"));
|
||||
registerBuiltInCommand(new OffhandCommand(geyser, "offhand", "geyser.commands.offhand.desc", "geyser.command.offhand"));
|
||||
registerBuiltInCommand(new DumpCommand(geyser, "dump", "geyser.commands.dump.desc", "geyser.command.dump"));
|
||||
registerBuiltInCommand(new VersionCommand(geyser, "version", "geyser.commands.version.desc", "geyser.command.version"));
|
||||
registerBuiltInCommand(new SettingsCommand(geyser, "settings", "geyser.commands.settings.desc", "geyser.command.settings"));
|
||||
registerBuiltInCommand(new StatisticsCommand(geyser, "statistics", "geyser.commands.statistics.desc", "geyser.command.statistics"));
|
||||
registerBuiltInCommand(new AdvancementsCommand("advancements", "geyser.commands.advancements.desc", "geyser.command.advancements"));
|
||||
registerBuiltInCommand(new AdvancedTooltipsCommand("tooltips", "geyser.commands.advancedtooltips.desc", "geyser.command.tooltips"));
|
||||
registerBuiltInCommand(new ConnectionTestCommand(geyser, "connectiontest", "geyser.commands.connectiontest.desc", "geyser.command.connectiontest"));
|
||||
if (this.geyser.getPlatformType() == PlatformType.STANDALONE) {
|
||||
registerBuiltInCommand(new StopCommand(geyser, "stop", "geyser.commands.stop.desc", "geyser.command.stop"));
|
||||
}
|
||||
|
||||
if (!this.geyser.extensionManager().extensions().isEmpty()) {
|
||||
registerBuiltInCommand(new ExtensionsCommand(this.geyser, "extensions", "geyser.commands.extensions.desc", "geyser.command.extensions"));
|
||||
}
|
||||
|
||||
GeyserDefineCommandsEvent defineCommandsEvent = new GeyserDefineCommandsEventImpl(this.commands) {
|
||||
|
||||
@Override
|
||||
public void register(@NonNull Command command) {
|
||||
if (!(command instanceof GeyserExtensionCommand extensionCommand)) {
|
||||
throw new IllegalArgumentException("Expected GeyserExtensionCommand as part of command registration but got " + command + "! Did you use the Command builder properly?");
|
||||
}
|
||||
|
||||
registerExtensionCommand(extensionCommand.extension(), extensionCommand);
|
||||
}
|
||||
};
|
||||
|
||||
this.geyser.eventBus().fire(defineCommandsEvent);
|
||||
|
||||
// Register help commands for all extensions with commands
|
||||
for (Map.Entry<Extension, Map<String, Command>> entry : this.extensionCommands.entrySet()) {
|
||||
String id = entry.getKey().description().id();
|
||||
registerExtensionCommand(entry.getKey(), new HelpCommand(this.geyser, "help", "geyser.commands.exthelp.desc", "geyser.command.exthelp." + id, id, entry.getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For internal Geyser commands
|
||||
*/
|
||||
public void registerBuiltInCommand(GeyserCommand command) {
|
||||
register(command, this.commands);
|
||||
}
|
||||
|
||||
public void registerExtensionCommand(@NonNull Extension extension, @NonNull Command command) {
|
||||
register(command, this.extensionCommands.computeIfAbsent(extension, e -> new HashMap<>()));
|
||||
}
|
||||
|
||||
private void register(Command command, Map<String, Command> commands) {
|
||||
commands.put(command.name(), command);
|
||||
geyser.getLogger().debug(GeyserLocale.getLocaleStringLog("geyser.commands.registered", command.name()));
|
||||
|
||||
if (command.aliases().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (String alias : command.aliases()) {
|
||||
commands.put(alias, command);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Map<String, Command> commands() {
|
||||
return Collections.unmodifiableMap(this.commands);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Map<Extension, Map<String, Command>> extensionCommands() {
|
||||
return Collections.unmodifiableMap(this.extensionCommands);
|
||||
}
|
||||
|
||||
public boolean runCommand(GeyserCommandSource sender, String command) {
|
||||
Extension extension = null;
|
||||
for (Extension loopedExtension : this.extensionCommands.keySet()) {
|
||||
if (command.startsWith(loopedExtension.description().id() + " ")) {
|
||||
extension = loopedExtension;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!command.startsWith("geyser ") && extension == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
command = command.trim().replace(extension != null ? extension.description().id() + " " : "geyser ", "");
|
||||
String label;
|
||||
String[] args;
|
||||
|
||||
if (!command.contains(" ")) {
|
||||
label = command.toLowerCase(Locale.ROOT);
|
||||
args = new String[0];
|
||||
} else {
|
||||
label = command.substring(0, command.indexOf(" ")).toLowerCase(Locale.ROOT);
|
||||
String argLine = command.substring(command.indexOf(" ") + 1);
|
||||
args = argLine.contains(" ") ? argLine.split(" ") : new String[] { argLine };
|
||||
}
|
||||
|
||||
Command cmd = (extension != null ? this.extensionCommands.getOrDefault(extension, Collections.emptyMap()) : this.commands).get(label);
|
||||
if (cmd == null) {
|
||||
sender.sendMessage(GeyserLocale.getLocaleStringLog("geyser.commands.invalid"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cmd instanceof GeyserCommand) {
|
||||
if (sender instanceof GeyserSession) {
|
||||
((GeyserCommand) cmd).execute((GeyserSession) sender, sender, args);
|
||||
} else {
|
||||
if (!cmd.isBedrockOnly()) {
|
||||
((GeyserCommand) cmd).execute(null, sender, args);
|
||||
} else {
|
||||
geyser.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.bootstrap.command.bedrock_only"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the description of the given command
|
||||
*
|
||||
* @param command Command to get the description for
|
||||
* @return Command description
|
||||
*/
|
||||
public String description(String command) {
|
||||
return "";
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public static class CommandBuilder<T extends CommandSource> implements Command.Builder<T> {
|
||||
private final Extension extension;
|
||||
private Class<? extends T> sourceType;
|
||||
private String name;
|
||||
private String description = "";
|
||||
private String permission = "";
|
||||
private List<String> aliases;
|
||||
private boolean suggestedOpOnly = false;
|
||||
private boolean executableOnConsole = true;
|
||||
private List<String> subCommands;
|
||||
private boolean bedrockOnly;
|
||||
private CommandExecutor<T> executor;
|
||||
|
||||
@Override
|
||||
public Command.Builder<T> source(@NonNull Class<? extends T> sourceType) {
|
||||
this.sourceType = sourceType;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandBuilder<T> name(@NonNull String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandBuilder<T> description(@NonNull String description) {
|
||||
this.description = description;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandBuilder<T> permission(@NonNull String permission) {
|
||||
this.permission = permission;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandBuilder<T> aliases(@NonNull List<String> aliases) {
|
||||
this.aliases = aliases;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Command.Builder<T> suggestedOpOnly(boolean suggestedOpOnly) {
|
||||
this.suggestedOpOnly = suggestedOpOnly;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandBuilder<T> executableOnConsole(boolean executableOnConsole) {
|
||||
this.executableOnConsole = executableOnConsole;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandBuilder<T> subCommands(@NonNull List<String> subCommands) {
|
||||
this.subCommands = subCommands;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandBuilder<T> bedrockOnly(boolean bedrockOnly) {
|
||||
this.bedrockOnly = bedrockOnly;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandBuilder<T> executor(@NonNull CommandExecutor<T> executor) {
|
||||
this.executor = executor;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public GeyserExtensionCommand build() {
|
||||
if (this.name == null || this.name.isBlank()) {
|
||||
throw new IllegalArgumentException("Command cannot be null or blank!");
|
||||
}
|
||||
|
||||
if (this.sourceType == null) {
|
||||
throw new IllegalArgumentException("Source type was not defined for command " + this.name + " in extension " + this.extension.name());
|
||||
}
|
||||
|
||||
return new GeyserExtensionCommand(this.extension, this.name, this.description, this.permission) {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void execute(@Nullable GeyserSession session, GeyserCommandSource sender, String[] args) {
|
||||
Class<? extends T> sourceType = CommandBuilder.this.sourceType;
|
||||
CommandExecutor<T> executor = CommandBuilder.this.executor;
|
||||
if (sourceType.isInstance(session)) {
|
||||
executor.execute((T) session, this, args);
|
||||
return;
|
||||
}
|
||||
|
||||
if (sourceType.isInstance(sender)) {
|
||||
executor.execute((T) sender, this, args);
|
||||
return;
|
||||
}
|
||||
|
||||
GeyserImpl.getInstance().getLogger().debug("Ignoring command " + this.name + " due to no suitable sender.");
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public List<String> aliases() {
|
||||
return CommandBuilder.this.aliases == null ? Collections.emptyList() : CommandBuilder.this.aliases;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSuggestedOpOnly() {
|
||||
return CommandBuilder.this.suggestedOpOnly;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public List<String> subCommands() {
|
||||
return CommandBuilder.this.subCommands == null ? Collections.emptyList() : CommandBuilder.this.subCommands;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBedrockOnly() {
|
||||
return CommandBuilder.this.bedrockOnly;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExecutableOnConsole() {
|
||||
return CommandBuilder.this.executableOnConsole;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -25,11 +25,16 @@
|
|||
|
||||
package org.geysermc.geyser.command;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.command.CommandSource;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Implemented on top of any class that can send a command.
|
||||
* For example, it wraps around Spigot's CommandSender class.
|
||||
|
@ -46,4 +51,29 @@ public interface GeyserCommandSource extends CommandSource {
|
|||
default void sendMessage(Component message) {
|
||||
sendMessage(LegacyComponentSerializer.legacySection().serialize(message));
|
||||
}
|
||||
|
||||
default void sendLocaleString(String key, Object... values) {
|
||||
sendMessage(GeyserLocale.getPlayerLocaleString(key, locale(), values));
|
||||
}
|
||||
|
||||
default void sendLocaleString(String key) {
|
||||
sendMessage(GeyserLocale.getPlayerLocaleString(key, locale()));
|
||||
}
|
||||
|
||||
@Override
|
||||
default @Nullable GeyserSession connection() {
|
||||
UUID uuid = playerUuid();
|
||||
if (uuid == null) {
|
||||
return null;
|
||||
}
|
||||
return GeyserImpl.getInstance().connectionByUuid(uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the underlying platform handle that this source represents.
|
||||
* If such handle doesn't exist, this itself is returned.
|
||||
*/
|
||||
default Object handle() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2023 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.geyser.command;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.incendo.cloud.CommandManager;
|
||||
import org.incendo.cloud.key.CloudKey;
|
||||
import org.incendo.cloud.permission.Permission;
|
||||
import org.incendo.cloud.permission.PermissionResult;
|
||||
import org.incendo.cloud.permission.PredicatePermission;
|
||||
|
||||
import static org.geysermc.geyser.command.GeyserPermission.Result.Meta;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class GeyserPermission implements PredicatePermission<GeyserCommandSource> {
|
||||
|
||||
/**
|
||||
* True if this permission requires the command source to be a bedrock player
|
||||
*/
|
||||
private final boolean bedrockOnly;
|
||||
|
||||
/**
|
||||
* True if this permission requires the command source to be any player
|
||||
*/
|
||||
private final boolean playerOnly;
|
||||
|
||||
/**
|
||||
* The permission node that the command source must have
|
||||
*/
|
||||
private final String permission;
|
||||
|
||||
/**
|
||||
* The command manager to delegate permission checks to
|
||||
*/
|
||||
private final CommandManager<GeyserCommandSource> manager;
|
||||
|
||||
@Override
|
||||
public @NonNull Result testPermission(@NonNull GeyserCommandSource source) {
|
||||
if (bedrockOnly) {
|
||||
if (source.connection() == null) {
|
||||
return new Result(Meta.NOT_BEDROCK);
|
||||
}
|
||||
// connection is present -> it is a player -> playerOnly is irrelevant
|
||||
} else if (playerOnly) {
|
||||
if (source.isConsole()) {
|
||||
return new Result(Meta.NOT_PLAYER); // must be a player but is console
|
||||
}
|
||||
}
|
||||
|
||||
if (permission.isBlank() || manager.hasPermission(source, permission)) {
|
||||
return new Result(Meta.ALLOWED);
|
||||
}
|
||||
return new Result(Meta.NO_PERMISSION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull CloudKey<Void> key() {
|
||||
return CloudKey.cloudKey(permission);
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic implementation of cloud's {@link PermissionResult} that delegates to the more informative {@link Meta}.
|
||||
*/
|
||||
public final class Result implements PermissionResult {
|
||||
|
||||
private final Meta meta;
|
||||
|
||||
private Result(Meta meta) {
|
||||
this.meta = meta;
|
||||
}
|
||||
|
||||
public Meta meta() {
|
||||
return meta;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean allowed() {
|
||||
return meta == Meta.ALLOWED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Permission permission() {
|
||||
return GeyserPermission.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* More detailed explanation of whether the permission check passed.
|
||||
*/
|
||||
public enum Meta {
|
||||
|
||||
/**
|
||||
* The source must be a bedrock player, but is not.
|
||||
*/
|
||||
NOT_BEDROCK,
|
||||
|
||||
/**
|
||||
* The source must be a player, but is not.
|
||||
*/
|
||||
NOT_PLAYER,
|
||||
|
||||
/**
|
||||
* The source does not have a required permission node.
|
||||
*/
|
||||
NO_PERMISSION,
|
||||
|
||||
/**
|
||||
* The source meets all requirements.
|
||||
*/
|
||||
ALLOWED
|
||||
}
|
||||
}
|
||||
}
|
|
@ -25,33 +25,32 @@
|
|||
|
||||
package org.geysermc.geyser.command.defaults;
|
||||
|
||||
import org.geysermc.geyser.api.util.TriState;
|
||||
import org.geysermc.geyser.command.GeyserCommand;
|
||||
import org.geysermc.geyser.command.GeyserCommandSource;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.text.ChatColor;
|
||||
import org.geysermc.geyser.text.MinecraftLocale;
|
||||
import org.incendo.cloud.context.CommandContext;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class AdvancedTooltipsCommand extends GeyserCommand {
|
||||
|
||||
public AdvancedTooltipsCommand(String name, String description, String permission) {
|
||||
super(name, description, permission);
|
||||
super(name, description, permission, TriState.TRUE, true, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) {
|
||||
if (session != null) {
|
||||
String onOrOff = session.isAdvancedTooltips() ? "off" : "on";
|
||||
session.setAdvancedTooltips(!session.isAdvancedTooltips());
|
||||
session.sendMessage("§l§e" + MinecraftLocale.getLocaleString("debug.prefix", session.locale()) + " §r" + MinecraftLocale.getLocaleString("debug.advanced_tooltips." + onOrOff, session.locale()));
|
||||
session.getInventoryTranslator().updateInventory(session, session.getPlayerInventory());
|
||||
}
|
||||
}
|
||||
public void execute(CommandContext<GeyserCommandSource> context) {
|
||||
GeyserSession session = Objects.requireNonNull(context.sender().connection());
|
||||
|
||||
@Override
|
||||
public boolean isExecutableOnConsole() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBedrockOnly() {
|
||||
return true;
|
||||
String onOrOff = session.isAdvancedTooltips() ? "off" : "on";
|
||||
session.setAdvancedTooltips(!session.isAdvancedTooltips());
|
||||
session.sendMessage(ChatColor.BOLD + ChatColor.YELLOW
|
||||
+ MinecraftLocale.getLocaleString("debug.prefix", session.locale())
|
||||
+ " " + ChatColor.RESET
|
||||
+ MinecraftLocale.getLocaleString("debug.advanced_tooltips." + onOrOff, session.locale()));
|
||||
session.getInventoryTranslator().updateInventory(session, session.getPlayerInventory());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,29 +25,23 @@
|
|||
|
||||
package org.geysermc.geyser.command.defaults;
|
||||
|
||||
import org.geysermc.geyser.api.util.TriState;
|
||||
import org.geysermc.geyser.command.GeyserCommand;
|
||||
import org.geysermc.geyser.command.GeyserCommandSource;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.incendo.cloud.context.CommandContext;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class AdvancementsCommand extends GeyserCommand {
|
||||
|
||||
public AdvancementsCommand(String name, String description, String permission) {
|
||||
super(name, description, permission);
|
||||
super(name, description, permission, TriState.TRUE, true, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) {
|
||||
if (session != null) {
|
||||
session.getAdvancementsCache().buildAndShowMenuForm();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExecutableOnConsole() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBedrockOnly() {
|
||||
return true;
|
||||
public void execute(CommandContext<GeyserCommandSource> context) {
|
||||
GeyserSession session = Objects.requireNonNull(context.sender().connection());
|
||||
session.getAdvancementsCache().buildAndShowMenuForm();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,90 +26,82 @@
|
|||
package org.geysermc.geyser.command.defaults;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.api.util.TriState;
|
||||
import org.geysermc.geyser.command.GeyserCommand;
|
||||
import org.geysermc.geyser.command.GeyserCommandSource;
|
||||
import org.geysermc.geyser.configuration.GeyserConfiguration;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
import org.geysermc.geyser.util.LoopbackUtil;
|
||||
import org.geysermc.geyser.util.WebUtils;
|
||||
import org.incendo.cloud.CommandManager;
|
||||
import org.incendo.cloud.context.CommandContext;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import static org.incendo.cloud.parser.standard.IntegerParser.integerParser;
|
||||
import static org.incendo.cloud.parser.standard.StringParser.stringParser;
|
||||
|
||||
public class ConnectionTestCommand extends GeyserCommand {
|
||||
|
||||
/*
|
||||
* The MOTD is temporarily changed during the connection test.
|
||||
* This allows us to check if we are pinging the correct Geyser instance
|
||||
*/
|
||||
public static String CONNECTION_TEST_MOTD = null;
|
||||
|
||||
private final GeyserImpl geyser;
|
||||
private static final String ADDRESS = "address";
|
||||
private static final String PORT = "port";
|
||||
|
||||
private final GeyserImpl geyser;
|
||||
private final Random random = new Random();
|
||||
|
||||
public ConnectionTestCommand(GeyserImpl geyser, String name, String description, String permission) {
|
||||
super(name, description, permission);
|
||||
super(name, description, permission, TriState.NOT_SET);
|
||||
this.geyser = geyser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(@Nullable GeyserSession session, GeyserCommandSource sender, String[] args) {
|
||||
// Only allow the console to create dumps on Geyser Standalone
|
||||
if (!sender.isConsole() && geyser.getPlatformType() == PlatformType.STANDALONE) {
|
||||
sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.locale()));
|
||||
return;
|
||||
}
|
||||
public void register(CommandManager<GeyserCommandSource> manager) {
|
||||
manager.command(baseBuilder(manager)
|
||||
.required(ADDRESS, stringParser())
|
||||
.optional(PORT, integerParser(0, 65535))
|
||||
.handler(this::execute));
|
||||
}
|
||||
|
||||
if (args.length == 0) {
|
||||
sender.sendMessage("Provide the server IP and port you are trying to test Bedrock connections for. Example: `test.geysermc.org:19132`");
|
||||
return;
|
||||
}
|
||||
@Override
|
||||
public void execute(CommandContext<GeyserCommandSource> context) {
|
||||
GeyserCommandSource source = context.sender();
|
||||
String ipArgument = context.get(ADDRESS);
|
||||
Integer portArgument = context.getOrDefault(PORT, null); // null if port was not specified
|
||||
|
||||
// Replace "<" and ">" symbols if they are present to avoid the common issue of people including them
|
||||
String[] fullAddress = args[0].replace("<", "").replace(">", "").split(":", 2);
|
||||
|
||||
// Still allow people to not supply a port and fallback to 19132
|
||||
int port;
|
||||
if (fullAddress.length == 2) {
|
||||
try {
|
||||
port = Integer.parseInt(fullAddress[1]);
|
||||
} catch (NumberFormatException e) {
|
||||
// can occur if e.g. "/geyser connectiontest <ip>:<port> is ran
|
||||
sender.sendMessage("Not a valid port! Specify a valid numeric port.");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
port = geyser.getConfig().getBedrock().broadcastPort();
|
||||
}
|
||||
String ip = fullAddress[0];
|
||||
final String ip = ipArgument.replace("<", "").replace(">", "");
|
||||
final int port = portArgument != null ? portArgument : geyser.getConfig().getBedrock().broadcastPort(); // default bedrock port
|
||||
|
||||
// Issue: people commonly checking placeholders
|
||||
if (ip.equals("ip")) {
|
||||
sender.sendMessage(ip + " is not a valid IP, and instead a placeholder. Please specify the IP to check.");
|
||||
source.sendMessage(ip + " is not a valid IP, and instead a placeholder. Please specify the IP to check.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Issue: checking 0.0.0.0 won't work
|
||||
if (ip.equals("0.0.0.0")) {
|
||||
sender.sendMessage("Please specify the IP that you would connect with. 0.0.0.0 in the config tells Geyser to the listen on the server's IPv4.");
|
||||
source.sendMessage("Please specify the IP that you would connect with. 0.0.0.0 in the config tells Geyser to the listen on the server's IPv4.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Issue: people testing local ip
|
||||
if (ip.equals("localhost") || ip.startsWith("127.") || ip.startsWith("10.") || ip.startsWith("192.168.")) {
|
||||
sender.sendMessage("This tool checks if connections from other networks are possible, so you cannot check a local IP.");
|
||||
source.sendMessage("This tool checks if connections from other networks are possible, so you cannot check a local IP.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Issue: port out of bounds
|
||||
if (port <= 0 || port >= 65535) {
|
||||
sender.sendMessage("The port you specified is invalid! Please specify a valid port.");
|
||||
source.sendMessage("The port you specified is invalid! Please specify a valid port.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -118,37 +110,37 @@ public class ConnectionTestCommand extends GeyserCommand {
|
|||
// Issue: do the ports not line up? We only check this if players don't override the broadcast port - if they do, they (hopefully) know what they're doing
|
||||
if (config.getBedrock().broadcastPort() == config.getBedrock().port()) {
|
||||
if (port != config.getBedrock().port()) {
|
||||
if (fullAddress.length == 2) {
|
||||
sender.sendMessage("The port you are testing with (" + port + ") is not the same as you set in your Geyser configuration ("
|
||||
if (portArgument != null) {
|
||||
source.sendMessage("The port you are testing with (" + port + ") is not the same as you set in your Geyser configuration ("
|
||||
+ config.getBedrock().port() + ")");
|
||||
sender.sendMessage("Re-run the command with the port in the config, or change the `bedrock` `port` in the config.");
|
||||
source.sendMessage("Re-run the command with the port in the config, or change the `bedrock` `port` in the config.");
|
||||
if (config.getBedrock().isCloneRemotePort()) {
|
||||
sender.sendMessage("You have `clone-remote-port` enabled. This option ignores the `bedrock` `port` in the config, and uses the Java server port instead.");
|
||||
source.sendMessage("You have `clone-remote-port` enabled. This option ignores the `bedrock` `port` in the config, and uses the Java server port instead.");
|
||||
}
|
||||
} else {
|
||||
sender.sendMessage("You did not specify the port to check (add it with \":<port>\"), " +
|
||||
source.sendMessage("You did not specify the port to check (add it with \":<port>\"), " +
|
||||
"and the default port 19132 does not match the port in your Geyser configuration ("
|
||||
+ config.getBedrock().port() + ")!");
|
||||
sender.sendMessage("Re-run the command with that port, or change the port in the config under `bedrock` `port`.");
|
||||
source.sendMessage("Re-run the command with that port, or change the port in the config under `bedrock` `port`.");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (config.getBedrock().broadcastPort() != port) {
|
||||
sender.sendMessage("The port you are testing with (" + port + ") is not the same as the broadcast port set in your Geyser configuration ("
|
||||
source.sendMessage("The port you are testing with (" + port + ") is not the same as the broadcast port set in your Geyser configuration ("
|
||||
+ config.getBedrock().broadcastPort() + "). ");
|
||||
sender.sendMessage("You ONLY need to change the broadcast port if clients connects with a port different from the port Geyser is running on.");
|
||||
sender.sendMessage("Re-run the command with the port in the config, or change the `bedrock` `broadcast-port` in the config.");
|
||||
source.sendMessage("You ONLY need to change the broadcast port if clients connects with a port different from the port Geyser is running on.");
|
||||
source.sendMessage("Re-run the command with the port in the config, or change the `bedrock` `broadcast-port` in the config.");
|
||||
}
|
||||
}
|
||||
|
||||
// Issue: is the `bedrock` `address` in the config different?
|
||||
if (!config.getBedrock().address().equals("0.0.0.0")) {
|
||||
sender.sendMessage("The address specified in `bedrock` `address` is not \"0.0.0.0\" - this may cause issues unless this is deliberate and intentional.");
|
||||
source.sendMessage("The address specified in `bedrock` `address` is not \"0.0.0.0\" - this may cause issues unless this is deliberate and intentional.");
|
||||
}
|
||||
|
||||
// Issue: did someone turn on enable-proxy-protocol, and they didn't mean it?
|
||||
if (config.getBedrock().isEnableProxyProtocol()) {
|
||||
sender.sendMessage("You have the `enable-proxy-protocol` setting enabled. " +
|
||||
source.sendMessage("You have the `enable-proxy-protocol` setting enabled. " +
|
||||
"Unless you're deliberately using additional software that REQUIRES this setting, you may not need it enabled.");
|
||||
}
|
||||
|
||||
|
@ -157,14 +149,14 @@ public class ConnectionTestCommand extends GeyserCommand {
|
|||
// Issue: SRV record?
|
||||
String[] record = WebUtils.findSrvRecord(geyser, ip);
|
||||
if (record != null && !ip.equals(record[3]) && !record[2].equals(String.valueOf(port))) {
|
||||
sender.sendMessage("Bedrock Edition does not support SRV records. Try connecting to your server using the address " + record[3] + " and the port " + record[2]
|
||||
source.sendMessage("Bedrock Edition does not support SRV records. Try connecting to your server using the address " + record[3] + " and the port " + record[2]
|
||||
+ ". If that fails, re-run this command with that address and port.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Issue: does Loopback need applying?
|
||||
if (LoopbackUtil.needsLoopback(GeyserImpl.getInstance().getLogger())) {
|
||||
sender.sendMessage("Loopback is not applied on this computer! You will have issues connecting from the same computer. " +
|
||||
source.sendMessage("Loopback is not applied on this computer! You will have issues connecting from the same computer. " +
|
||||
"See here for steps on how to resolve: " + "https://wiki.geysermc.org/geyser/fixing-unable-to-connect-to-world/#using-geyser-on-the-same-computer");
|
||||
}
|
||||
|
||||
|
@ -178,7 +170,7 @@ public class ConnectionTestCommand extends GeyserCommand {
|
|||
String connectionTestMotd = "Geyser Connection Test " + randomStr;
|
||||
CONNECTION_TEST_MOTD = connectionTestMotd;
|
||||
|
||||
sender.sendMessage("Testing server connection to " + ip + " with port: " + port + " now. Please wait...");
|
||||
source.sendMessage("Testing server connection to " + ip + " with port: " + port + " now. Please wait...");
|
||||
JsonNode output;
|
||||
try {
|
||||
String hostname = URLEncoder.encode(ip, StandardCharsets.UTF_8);
|
||||
|
@ -200,31 +192,31 @@ public class ConnectionTestCommand extends GeyserCommand {
|
|||
JsonNode pong = ping.get("pong");
|
||||
String remoteMotd = pong.get("motd").asText();
|
||||
if (!connectionTestMotd.equals(remoteMotd)) {
|
||||
sender.sendMessage("The MOTD did not match when we pinged the server (we got '" + remoteMotd + "'). " +
|
||||
source.sendMessage("The MOTD did not match when we pinged the server (we got '" + remoteMotd + "'). " +
|
||||
"Did you supply the correct IP and port of your server?");
|
||||
sendLinks(sender);
|
||||
sendLinks(source);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ping.get("tcpFirst").asBoolean()) {
|
||||
sender.sendMessage("Your server hardware likely has some sort of firewall preventing people from joining easily. See https://geysermc.link/ovh-firewall for more information.");
|
||||
sendLinks(sender);
|
||||
source.sendMessage("Your server hardware likely has some sort of firewall preventing people from joining easily. See https://geysermc.link/ovh-firewall for more information.");
|
||||
sendLinks(source);
|
||||
return;
|
||||
}
|
||||
|
||||
sender.sendMessage("Your server is likely online and working as of " + when + "!");
|
||||
sendLinks(sender);
|
||||
source.sendMessage("Your server is likely online and working as of " + when + "!");
|
||||
sendLinks(source);
|
||||
return;
|
||||
}
|
||||
|
||||
sender.sendMessage("Your server is likely unreachable from outside the network!");
|
||||
source.sendMessage("Your server is likely unreachable from outside the network!");
|
||||
JsonNode message = output.get("message");
|
||||
if (message != null && !message.asText().isEmpty()) {
|
||||
sender.sendMessage("Got the error message: " + message.asText());
|
||||
source.sendMessage("Got the error message: " + message.asText());
|
||||
}
|
||||
sendLinks(sender);
|
||||
sendLinks(source);
|
||||
} catch (Exception e) {
|
||||
sender.sendMessage("An error occurred while trying to check your connection! Check the console for more information.");
|
||||
source.sendMessage("An error occurred while trying to check your connection! Check the console for more information.");
|
||||
geyser.getLogger().error("Error while trying to check your connection!", e);
|
||||
}
|
||||
});
|
||||
|
@ -235,9 +227,4 @@ public class ConnectionTestCommand extends GeyserCommand {
|
|||
"https://wiki.geysermc.org/geyser/setup/");
|
||||
sender.sendMessage("If that does not work, see " + "https://wiki.geysermc.org/geyser/fixing-unable-to-connect-to-world/" + ", or contact us on Discord: " + "https://discord.gg/geysermc");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSuggestedOpOnly() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,43 +29,71 @@ import com.fasterxml.jackson.core.util.DefaultIndenter;
|
|||
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.util.TriState;
|
||||
import org.geysermc.geyser.command.GeyserCommand;
|
||||
import org.geysermc.geyser.command.GeyserCommandSource;
|
||||
import org.geysermc.geyser.dump.DumpInfo;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.text.AsteriskSerializer;
|
||||
import org.geysermc.geyser.text.ChatColor;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
import org.geysermc.geyser.util.WebUtils;
|
||||
import org.incendo.cloud.CommandManager;
|
||||
import org.incendo.cloud.context.CommandContext;
|
||||
import org.incendo.cloud.suggestion.SuggestionProvider;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.incendo.cloud.parser.standard.StringArrayParser.stringArrayParser;
|
||||
|
||||
public class DumpCommand extends GeyserCommand {
|
||||
|
||||
private static final String ARGUMENTS = "args";
|
||||
private static final Iterable<String> SUGGESTIONS = List.of("full", "offline", "logs");
|
||||
|
||||
private final GeyserImpl geyser;
|
||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||
private static final String DUMP_URL = "https://dump.geysermc.org/";
|
||||
|
||||
public DumpCommand(GeyserImpl geyser, String name, String description, String permission) {
|
||||
super(name, description, permission);
|
||||
|
||||
super(name, description, permission, TriState.NOT_SET);
|
||||
this.geyser = geyser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) {
|
||||
// Only allow the console to create dumps on Geyser Standalone
|
||||
if (!sender.isConsole() && geyser.getPlatformType() == PlatformType.STANDALONE) {
|
||||
sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.locale()));
|
||||
return;
|
||||
@Override
|
||||
public void register(CommandManager<GeyserCommandSource> manager) {
|
||||
manager.command(baseBuilder(manager)
|
||||
.optional(ARGUMENTS, stringArrayParser(), SuggestionProvider.blockingStrings((ctx, input) -> {
|
||||
// parse suggestions here
|
||||
List<String> inputs = new ArrayList<>();
|
||||
while (input.hasRemainingInput()) {
|
||||
inputs.add(input.readStringSkipWhitespace());
|
||||
}
|
||||
|
||||
if (inputs.size() <= 2) {
|
||||
return SUGGESTIONS; // only `geyser dump` was typed (2 literals)
|
||||
}
|
||||
|
||||
// the rest of the input after `geyser dump` is for this argument
|
||||
inputs = inputs.subList(2, inputs.size());
|
||||
|
||||
// don't suggest any words they have already typed
|
||||
List<String> suggestions = new ArrayList<>();
|
||||
SUGGESTIONS.forEach(suggestions::add);
|
||||
suggestions.removeAll(inputs);
|
||||
return suggestions;
|
||||
}))
|
||||
.handler(this::execute));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext<GeyserCommandSource> context) {
|
||||
GeyserCommandSource source = context.sender();
|
||||
String[] args = context.getOrDefault(ARGUMENTS, new String[0]);
|
||||
|
||||
boolean showSensitive = false;
|
||||
boolean offlineDump = false;
|
||||
boolean addLog = false;
|
||||
|
@ -75,13 +103,14 @@ public class DumpCommand extends GeyserCommand {
|
|||
case "full" -> showSensitive = true;
|
||||
case "offline" -> offlineDump = true;
|
||||
case "logs" -> addLog = true;
|
||||
default -> context.sender().sendMessage("Invalid geyser dump option " + arg + "! Fallback to no arguments.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AsteriskSerializer.showSensitive = showSensitive;
|
||||
|
||||
sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.collecting", sender.locale()));
|
||||
source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.collecting", source.locale()));
|
||||
String dumpData;
|
||||
try {
|
||||
if (offlineDump) {
|
||||
|
@ -93,7 +122,7 @@ public class DumpCommand extends GeyserCommand {
|
|||
dumpData = MAPPER.writeValueAsString(new DumpInfo(addLog));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.collect_error", sender.locale()));
|
||||
source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.collect_error", source.locale()));
|
||||
geyser.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.commands.dump.collect_error_short"), e);
|
||||
return;
|
||||
}
|
||||
|
@ -101,21 +130,21 @@ public class DumpCommand extends GeyserCommand {
|
|||
String uploadedDumpUrl;
|
||||
|
||||
if (offlineDump) {
|
||||
sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.writing", sender.locale()));
|
||||
source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.writing", source.locale()));
|
||||
|
||||
try {
|
||||
FileOutputStream outputStream = new FileOutputStream(GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("dump.json").toFile());
|
||||
outputStream.write(dumpData.getBytes());
|
||||
outputStream.close();
|
||||
} catch (IOException e) {
|
||||
sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.write_error", sender.locale()));
|
||||
source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.write_error", source.locale()));
|
||||
geyser.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.commands.dump.write_error_short"), e);
|
||||
return;
|
||||
}
|
||||
|
||||
uploadedDumpUrl = "dump.json";
|
||||
} else {
|
||||
sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.uploading", sender.locale()));
|
||||
source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.uploading", source.locale()));
|
||||
|
||||
String response;
|
||||
JsonNode responseNode;
|
||||
|
@ -123,33 +152,22 @@ public class DumpCommand extends GeyserCommand {
|
|||
response = WebUtils.post(DUMP_URL + "documents", dumpData);
|
||||
responseNode = MAPPER.readTree(response);
|
||||
} catch (IOException e) {
|
||||
sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.upload_error", sender.locale()));
|
||||
source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.upload_error", source.locale()));
|
||||
geyser.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.commands.dump.upload_error_short"), e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!responseNode.has("key")) {
|
||||
sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.upload_error_short", sender.locale()) + ": " + (responseNode.has("message") ? responseNode.get("message").asText() : response));
|
||||
source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.upload_error_short", source.locale()) + ": " + (responseNode.has("message") ? responseNode.get("message").asText() : response));
|
||||
return;
|
||||
}
|
||||
|
||||
uploadedDumpUrl = DUMP_URL + responseNode.get("key").asText();
|
||||
}
|
||||
|
||||
sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.message", sender.locale()) + " " + ChatColor.DARK_AQUA + uploadedDumpUrl);
|
||||
if (!sender.isConsole()) {
|
||||
geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.commands.dump.created", sender.name(), uploadedDumpUrl));
|
||||
source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.message", source.locale()) + " " + ChatColor.DARK_AQUA + uploadedDumpUrl);
|
||||
if (!source.isConsole()) {
|
||||
geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.commands.dump.created", source.name(), uploadedDumpUrl));
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public List<String> subCommands() {
|
||||
return Arrays.asList("offline", "full", "logs");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSuggestedOpOnly() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,14 +25,14 @@
|
|||
|
||||
package org.geysermc.geyser.command.defaults;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.extension.Extension;
|
||||
import org.geysermc.geyser.api.util.TriState;
|
||||
import org.geysermc.geyser.command.GeyserCommand;
|
||||
import org.geysermc.geyser.command.GeyserCommandSource;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.text.ChatColor;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
import org.incendo.cloud.context.CommandContext;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
@ -41,22 +41,23 @@ public class ExtensionsCommand extends GeyserCommand {
|
|||
private final GeyserImpl geyser;
|
||||
|
||||
public ExtensionsCommand(GeyserImpl geyser, String name, String description, String permission) {
|
||||
super(name, description, permission);
|
||||
|
||||
super(name, description, permission, TriState.TRUE);
|
||||
this.geyser = geyser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(@Nullable GeyserSession session, GeyserCommandSource sender, String[] args) {
|
||||
public void execute(CommandContext<GeyserCommandSource> context) {
|
||||
GeyserCommandSource source = context.sender();
|
||||
|
||||
// TODO: Pagination
|
||||
int page = 1;
|
||||
int maxPage = 1;
|
||||
String header = GeyserLocale.getPlayerLocaleString("geyser.commands.extensions.header", sender.locale(), page, maxPage);
|
||||
sender.sendMessage(header);
|
||||
String header = GeyserLocale.getPlayerLocaleString("geyser.commands.extensions.header", source.locale(), page, maxPage);
|
||||
source.sendMessage(header);
|
||||
|
||||
this.geyser.extensionManager().extensions().stream().sorted(Comparator.comparing(Extension::name)).forEach(extension -> {
|
||||
String extensionName = (extension.isEnabled() ? ChatColor.GREEN : ChatColor.RED) + extension.name();
|
||||
sender.sendMessage("- " + extensionName + ChatColor.RESET + " v" + extension.description().version() + formatAuthors(extension.description().authors()));
|
||||
source.sendMessage("- " + extensionName + ChatColor.RESET + " v" + extension.description().version() + formatAuthors(extension.description().authors()));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -25,61 +25,59 @@
|
|||
|
||||
package org.geysermc.geyser.command.defaults;
|
||||
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import com.google.common.base.Predicates;
|
||||
import org.geysermc.geyser.api.command.Command;
|
||||
import org.geysermc.geyser.api.util.TriState;
|
||||
import org.geysermc.geyser.command.GeyserCommand;
|
||||
import org.geysermc.geyser.command.GeyserCommandSource;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.text.ChatColor;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
import org.incendo.cloud.context.CommandContext;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
|
||||
public class HelpCommand extends GeyserCommand {
|
||||
private final GeyserImpl geyser;
|
||||
private final String baseCommand;
|
||||
private final Map<String, Command> commands;
|
||||
private final String rootCommand;
|
||||
private final Collection<GeyserCommand> commands;
|
||||
|
||||
public HelpCommand(GeyserImpl geyser, String name, String description, String permission,
|
||||
String baseCommand, Map<String, Command> commands) {
|
||||
super(name, description, permission);
|
||||
this.geyser = geyser;
|
||||
this.baseCommand = baseCommand;
|
||||
this.commands = commands;
|
||||
|
||||
this.setAliases(Collections.singletonList("?"));
|
||||
public HelpCommand(String rootCommand, String name, String description, String permission, Map<String, GeyserCommand> commands) {
|
||||
super(name, description, permission, TriState.TRUE);
|
||||
this.rootCommand = rootCommand;
|
||||
this.commands = commands.values();
|
||||
this.aliases = Collections.singletonList("?");
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the help menu to a command sender. Will not show certain commands depending on the command sender and session.
|
||||
*
|
||||
* @param session The Geyser session of the command sender, if it is a bedrock player. If null, bedrock-only commands will be hidden.
|
||||
* @param sender The CommandSender to send the help message to.
|
||||
* @param args Not used.
|
||||
*/
|
||||
@Override
|
||||
public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) {
|
||||
public String rootCommand() {
|
||||
return rootCommand;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext<GeyserCommandSource> context) {
|
||||
execute(context.sender());
|
||||
}
|
||||
|
||||
public void execute(GeyserCommandSource source) {
|
||||
boolean bedrockPlayer = source.connection() != null;
|
||||
|
||||
// todo: pagination
|
||||
int page = 1;
|
||||
int maxPage = 1;
|
||||
String translationKey = this.baseCommand.equals("geyser") ? "geyser.commands.help.header" : "geyser.commands.extensions.header";
|
||||
String header = GeyserLocale.getPlayerLocaleString(translationKey, sender.locale(), page, maxPage);
|
||||
sender.sendMessage(header);
|
||||
String translationKey = this.rootCommand.equals(DEFAULT_ROOT_COMMAND) ? "geyser.commands.help.header" : "geyser.commands.extensions.header";
|
||||
String header = GeyserLocale.getPlayerLocaleString(translationKey, source.locale(), page, maxPage);
|
||||
source.sendMessage(header);
|
||||
|
||||
this.commands.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(entry -> {
|
||||
Command cmd = entry.getValue();
|
||||
|
||||
// Standalone hack-in since it doesn't have a concept of permissions
|
||||
if (geyser.getPlatformType() == PlatformType.STANDALONE || sender.hasPermission(cmd.permission())) {
|
||||
// Only list commands the player can actually run
|
||||
if (cmd.isBedrockOnly() && session == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
sender.sendMessage(ChatColor.YELLOW + "/" + baseCommand + " " + entry.getKey() + ChatColor.WHITE + ": " +
|
||||
GeyserLocale.getPlayerLocaleString(cmd.description(), sender.locale()));
|
||||
}
|
||||
});
|
||||
this.commands.stream()
|
||||
.distinct() // remove aliases
|
||||
.filter(bedrockPlayer ? Predicates.alwaysTrue() : cmd -> !cmd.isBedrockOnly()) // remove bedrock only commands if not a bedrock player
|
||||
.filter(cmd -> source.hasPermission(cmd.permission()))
|
||||
.sorted(Comparator.comparing(Command::name))
|
||||
.forEachOrdered(cmd -> {
|
||||
String description = GeyserLocale.getPlayerLocaleString(cmd.description(), source.locale());
|
||||
source.sendMessage(ChatColor.YELLOW + "/" + rootCommand + " " + cmd.name() + ChatColor.WHITE + ": " + description);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,10 +26,12 @@
|
|||
package org.geysermc.geyser.command.defaults;
|
||||
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.util.TriState;
|
||||
import org.geysermc.geyser.command.GeyserCommand;
|
||||
import org.geysermc.geyser.command.GeyserCommandSource;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
import org.incendo.cloud.context.CommandContext;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
@ -38,22 +40,18 @@ public class ListCommand extends GeyserCommand {
|
|||
private final GeyserImpl geyser;
|
||||
|
||||
public ListCommand(GeyserImpl geyser, String name, String description, String permission) {
|
||||
super(name, description, permission);
|
||||
|
||||
super(name, description, permission, TriState.NOT_SET);
|
||||
this.geyser = geyser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) {
|
||||
String message = GeyserLocale.getPlayerLocaleString("geyser.commands.list.message", sender.locale(),
|
||||
geyser.getSessionManager().size(),
|
||||
geyser.getSessionManager().getAllSessions().stream().map(GeyserSession::bedrockUsername).collect(Collectors.joining(" ")));
|
||||
public void execute(CommandContext<GeyserCommandSource> context) {
|
||||
GeyserCommandSource source = context.sender();
|
||||
|
||||
sender.sendMessage(message);
|
||||
}
|
||||
String message = GeyserLocale.getPlayerLocaleString("geyser.commands.list.message", source.locale(),
|
||||
geyser.getSessionManager().size(),
|
||||
geyser.getSessionManager().getAllSessions().stream().map(GeyserSession::bedrockUsername).collect(Collectors.joining(" ")));
|
||||
|
||||
@Override
|
||||
public boolean isSuggestedOpOnly() {
|
||||
return true;
|
||||
source.sendMessage(message);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,33 +25,23 @@
|
|||
|
||||
package org.geysermc.geyser.command.defaults;
|
||||
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.util.TriState;
|
||||
import org.geysermc.geyser.command.GeyserCommand;
|
||||
import org.geysermc.geyser.command.GeyserCommandSource;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.incendo.cloud.context.CommandContext;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class OffhandCommand extends GeyserCommand {
|
||||
|
||||
public OffhandCommand(GeyserImpl geyser, String name, String description, String permission) {
|
||||
super(name, description, permission);
|
||||
public OffhandCommand(String name, String description, String permission) {
|
||||
super(name, description, permission, TriState.TRUE, true, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) {
|
||||
if (session == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
public void execute(CommandContext<GeyserCommandSource> context) {
|
||||
GeyserSession session = Objects.requireNonNull(context.sender().connection());
|
||||
session.requestOffhandSwap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExecutableOnConsole() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBedrockOnly() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2023 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.geyser.command.defaults;
|
||||
|
||||
import org.geysermc.geyser.api.util.TriState;
|
||||
import org.geysermc.geyser.command.GeyserCommand;
|
||||
import org.geysermc.geyser.command.GeyserCommandSource;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
import org.incendo.cloud.context.CommandContext;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class PingCommand extends GeyserCommand {
|
||||
|
||||
public PingCommand(String name, String description, String permission) {
|
||||
super(name, description, permission, TriState.TRUE, true, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext<GeyserCommandSource> context) {
|
||||
GeyserSession session = Objects.requireNonNull(context.sender().connection());
|
||||
session.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.ping.message", session.locale(), session.ping()));
|
||||
}
|
||||
}
|
||||
|
|
@ -25,12 +25,12 @@
|
|||
|
||||
package org.geysermc.geyser.command.defaults;
|
||||
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.util.TriState;
|
||||
import org.geysermc.geyser.command.GeyserCommand;
|
||||
import org.geysermc.geyser.command.GeyserCommandSource;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
import org.incendo.cloud.context.CommandContext;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
|
@ -39,27 +39,17 @@ public class ReloadCommand extends GeyserCommand {
|
|||
private final GeyserImpl geyser;
|
||||
|
||||
public ReloadCommand(GeyserImpl geyser, String name, String description, String permission) {
|
||||
super(name, description, permission);
|
||||
super(name, description, permission, TriState.NOT_SET);
|
||||
this.geyser = geyser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) {
|
||||
if (!sender.isConsole() && geyser.getPlatformType() == PlatformType.STANDALONE) {
|
||||
return;
|
||||
}
|
||||
|
||||
String message = GeyserLocale.getPlayerLocaleString("geyser.commands.reload.message", sender.locale());
|
||||
|
||||
sender.sendMessage(message);
|
||||
public void execute(CommandContext<GeyserCommandSource> context) {
|
||||
GeyserCommandSource source = context.sender();
|
||||
source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.reload.message", source.locale()));
|
||||
|
||||
geyser.getSessionManager().disconnectAll("geyser.commands.reload.kick");
|
||||
//FIXME Without the tiny wait, players do not get kicked - same happens when Geyser tries to disconnect all sessions on shutdown
|
||||
geyser.getScheduledThread().schedule(geyser::reloadGeyser, 10, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSuggestedOpOnly() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,31 +25,24 @@
|
|||
|
||||
package org.geysermc.geyser.command.defaults;
|
||||
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.util.TriState;
|
||||
import org.geysermc.geyser.command.GeyserCommand;
|
||||
import org.geysermc.geyser.command.GeyserCommandSource;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.util.SettingsUtils;
|
||||
import org.incendo.cloud.context.CommandContext;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class SettingsCommand extends GeyserCommand {
|
||||
public SettingsCommand(GeyserImpl geyser, String name, String description, String permission) {
|
||||
super(name, description, permission);
|
||||
|
||||
public SettingsCommand(String name, String description, String permission) {
|
||||
super(name, description, permission, TriState.TRUE, true, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) {
|
||||
if (session != null) {
|
||||
session.sendForm(SettingsUtils.buildForm(session));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExecutableOnConsole() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBedrockOnly() {
|
||||
return true;
|
||||
public void execute(CommandContext<GeyserCommandSource> context) {
|
||||
GeyserSession session = Objects.requireNonNull(context.sender().connection());
|
||||
session.sendForm(SettingsUtils.buildForm(session));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,35 +25,28 @@
|
|||
|
||||
package org.geysermc.geyser.command.defaults;
|
||||
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.util.TriState;
|
||||
import org.geysermc.geyser.command.GeyserCommand;
|
||||
import org.geysermc.geyser.command.GeyserCommandSource;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.ClientCommand;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.ServerboundClientCommandPacket;
|
||||
import org.incendo.cloud.context.CommandContext;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class StatisticsCommand extends GeyserCommand {
|
||||
|
||||
public StatisticsCommand(GeyserImpl geyser, String name, String description, String permission) {
|
||||
super(name, description, permission);
|
||||
public StatisticsCommand(String name, String description, String permission) {
|
||||
super(name, description, permission, TriState.TRUE, true, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) {
|
||||
if (session == null) return;
|
||||
public void execute(CommandContext<GeyserCommandSource> context) {
|
||||
GeyserSession session = Objects.requireNonNull(context.sender().connection());
|
||||
|
||||
session.setWaitingForStatistics(true);
|
||||
ServerboundClientCommandPacket ServerboundClientCommandPacket = new ServerboundClientCommandPacket(ClientCommand.STATS);
|
||||
session.sendDownstreamGamePacket(ServerboundClientCommandPacket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExecutableOnConsole() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBedrockOnly() {
|
||||
return true;
|
||||
ServerboundClientCommandPacket packet = new ServerboundClientCommandPacket(ClientCommand.STATS);
|
||||
session.sendDownstreamGamePacket(packet);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,12 +25,11 @@
|
|||
|
||||
package org.geysermc.geyser.command.defaults;
|
||||
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.util.TriState;
|
||||
import org.geysermc.geyser.command.GeyserCommand;
|
||||
import org.geysermc.geyser.command.GeyserCommandSource;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
import org.incendo.cloud.context.CommandContext;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
|
@ -39,24 +38,13 @@ public class StopCommand extends GeyserCommand {
|
|||
private final GeyserImpl geyser;
|
||||
|
||||
public StopCommand(GeyserImpl geyser, String name, String description, String permission) {
|
||||
super(name, description, permission);
|
||||
super(name, description, permission, TriState.NOT_SET);
|
||||
this.geyser = geyser;
|
||||
|
||||
this.setAliases(Collections.singletonList("shutdown"));
|
||||
this.aliases = Collections.singletonList("shutdown");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) {
|
||||
if (!sender.isConsole() && geyser.getPlatformType() == PlatformType.STANDALONE) {
|
||||
sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.locale()));
|
||||
return;
|
||||
}
|
||||
|
||||
public void execute(CommandContext<GeyserCommandSource> context) {
|
||||
geyser.getBootstrap().onGeyserShutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSuggestedOpOnly() {
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -29,13 +29,14 @@ import com.fasterxml.jackson.databind.JsonNode;
|
|||
import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.api.util.TriState;
|
||||
import org.geysermc.geyser.command.GeyserCommand;
|
||||
import org.geysermc.geyser.command.GeyserCommandSource;
|
||||
import org.geysermc.geyser.network.GameProtocol;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.text.ChatColor;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
import org.geysermc.geyser.util.WebUtils;
|
||||
import org.incendo.cloud.context.CommandContext;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
@ -45,13 +46,14 @@ public class VersionCommand extends GeyserCommand {
|
|||
private final GeyserImpl geyser;
|
||||
|
||||
public VersionCommand(GeyserImpl geyser, String name, String description, String permission) {
|
||||
super(name, description, permission);
|
||||
|
||||
super(name, description, permission, TriState.NOT_SET);
|
||||
this.geyser = geyser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) {
|
||||
public void execute(CommandContext<GeyserCommandSource> context) {
|
||||
GeyserCommandSource source = context.sender();
|
||||
|
||||
String bedrockVersions;
|
||||
List<BedrockCodec> supportedCodecs = GameProtocol.SUPPORTED_BEDROCK_CODECS;
|
||||
if (supportedCodecs.size() > 1) {
|
||||
|
@ -67,45 +69,37 @@ public class VersionCommand extends GeyserCommand {
|
|||
javaVersions = supportedJavaVersions.get(0);
|
||||
}
|
||||
|
||||
sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.version", sender.locale(),
|
||||
source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.version", source.locale(),
|
||||
GeyserImpl.NAME, GeyserImpl.VERSION, javaVersions, bedrockVersions));
|
||||
|
||||
// Disable update checking in dev mode and for players in Geyser Standalone
|
||||
if (!GeyserImpl.getInstance().isProductionEnvironment() || (!sender.isConsole() && geyser.getPlatformType() == PlatformType.STANDALONE)) {
|
||||
if (!GeyserImpl.getInstance().isProductionEnvironment() || (!source.isConsole() && geyser.getPlatformType() == PlatformType.STANDALONE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (GeyserImpl.IS_DEV) {
|
||||
// TODO cloud use language string
|
||||
sender.sendMessage("You are running a development build of Geyser! Please report any bugs you find on our Discord server: %s"
|
||||
.formatted("https://discord.gg/geysermc"));
|
||||
//sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.core.dev_build", sender.locale(), "https://discord.gg/geysermc"));
|
||||
source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.core.dev_build", source.locale(), "https://discord.gg/geysermc"));
|
||||
return;
|
||||
}
|
||||
|
||||
sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.checking", sender.locale()));
|
||||
source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.checking", source.locale()));
|
||||
try {
|
||||
int buildNumber = this.geyser.buildNumber();
|
||||
JsonNode response = WebUtils.getJson("https://download.geysermc.org/v2/projects/geyser/versions/latest/builds/latest");
|
||||
int latestBuildNumber = response.get("build").asInt();
|
||||
|
||||
if (latestBuildNumber == buildNumber) {
|
||||
sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.no_updates", sender.locale()));
|
||||
source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.no_updates", source.locale()));
|
||||
return;
|
||||
}
|
||||
|
||||
sender.sendMessage(GeyserLocale.getPlayerLocaleString(
|
||||
source.sendMessage(GeyserLocale.getPlayerLocaleString(
|
||||
"geyser.commands.version.outdated",
|
||||
sender.locale(), (latestBuildNumber - buildNumber), "https://geysermc.org/download"
|
||||
source.locale(), (latestBuildNumber - buildNumber), "https://geysermc.org/download"
|
||||
));
|
||||
} catch (IOException e) {
|
||||
GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.commands.version.failed"), e);
|
||||
sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.version.failed", sender.locale()));
|
||||
source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.version.failed", source.locale()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSuggestedOpOnly() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2024 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.geyser.command.standalone;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
@Getter
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@SuppressWarnings("FieldMayBeFinal") // Jackson requires that the fields are not final
|
||||
public class PermissionConfiguration {
|
||||
|
||||
@JsonProperty("default-permissions")
|
||||
private Set<String> defaultPermissions = Collections.emptySet();
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2024 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.geyser.command.standalone;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionCheckersEvent;
|
||||
import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent;
|
||||
import org.geysermc.geyser.api.permission.PermissionChecker;
|
||||
import org.geysermc.geyser.api.util.TriState;
|
||||
import org.geysermc.geyser.command.CommandRegistry;
|
||||
import org.geysermc.geyser.command.GeyserCommandSource;
|
||||
import org.geysermc.geyser.util.FileUtils;
|
||||
import org.incendo.cloud.CommandManager;
|
||||
import org.incendo.cloud.execution.ExecutionCoordinator;
|
||||
import org.incendo.cloud.internal.CommandRegistrationHandler;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
public class StandaloneCloudCommandManager extends CommandManager<GeyserCommandSource> {
|
||||
|
||||
private final GeyserImpl geyser;
|
||||
|
||||
/**
|
||||
* The checkers we use to test if a command source has a permission
|
||||
*/
|
||||
private final List<PermissionChecker> permissionCheckers = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Any permissions that all connections have
|
||||
*/
|
||||
private final Set<String> basePermissions = new ObjectOpenHashSet<>();
|
||||
|
||||
public StandaloneCloudCommandManager(GeyserImpl geyser) {
|
||||
super(ExecutionCoordinator.simpleCoordinator(), CommandRegistrationHandler.nullCommandRegistrationHandler());
|
||||
// simpleCoordinator: execute commands immediately on the calling thread.
|
||||
// nullCommandRegistrationHandler: cloud is not responsible for handling our CommandRegistry, which is fairly decoupled.
|
||||
this.geyser = geyser;
|
||||
|
||||
// allow any extensions to customize permissions
|
||||
geyser.getEventBus().fire((GeyserRegisterPermissionCheckersEvent) permissionCheckers::add);
|
||||
|
||||
// must still implement a basic permission system
|
||||
try {
|
||||
File permissionsFile = geyser.getBootstrap().getConfigFolder().resolve("permissions.yml").toFile();
|
||||
FileUtils.fileOrCopiedFromResource(permissionsFile, "permissions.yml", geyser.getBootstrap());
|
||||
PermissionConfiguration config = FileUtils.loadConfig(permissionsFile, PermissionConfiguration.class);
|
||||
basePermissions.addAll(config.getDefaultPermissions());
|
||||
} catch (Exception e) {
|
||||
geyser.getLogger().error("Failed to load permissions.yml - proceeding without it", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire a {@link GeyserRegisterPermissionsEvent} to determine any additions or removals to the base list of
|
||||
* permissions. This should be called after any event listeners have been registered, such as that of {@link CommandRegistry}.
|
||||
*/
|
||||
public void fireRegisterPermissionsEvent() {
|
||||
geyser.getEventBus().fire((GeyserRegisterPermissionsEvent) (permission, def) -> {
|
||||
Objects.requireNonNull(permission, "permission");
|
||||
Objects.requireNonNull(def, "permission default for " + permission);
|
||||
|
||||
if (permission.isBlank()) {
|
||||
return;
|
||||
}
|
||||
if (def == TriState.TRUE) {
|
||||
basePermissions.add(permission);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(@NonNull GeyserCommandSource sender, @NonNull String permission) {
|
||||
// Note: the two GeyserCommandSources on Geyser-Standalone are GeyserLogger and GeyserSession
|
||||
// GeyserLogger#hasPermission always returns true
|
||||
// GeyserSession#hasPermission delegates to this method,
|
||||
// which is why this method doesn't just call GeyserCommandSource#hasPermission
|
||||
if (sender.isConsole()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// An empty or blank permission is treated as a lack of permission requirement
|
||||
if (permission.isBlank()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (PermissionChecker checker : permissionCheckers) {
|
||||
Boolean result = checker.hasPermission(sender, permission).toBoolean();
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
// undefined - try the next checker to see if it has a defined value
|
||||
}
|
||||
// fallback to our list of default permissions
|
||||
// note that a PermissionChecker may in fact override any values set here by returning FALSE
|
||||
return basePermissions.contains(permission);
|
||||
}
|
||||
}
|
|
@ -96,4 +96,9 @@ public class GeyserEntityData implements EntityData {
|
|||
public boolean isMovementLocked() {
|
||||
return !movementLockOwners.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void switchHands() {
|
||||
session.requestOffhandSwap();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,12 +35,12 @@ import java.util.Map;
|
|||
public abstract class GeyserDefineCommandsEventImpl implements GeyserDefineCommandsEvent {
|
||||
private final Map<String, Command> commands;
|
||||
|
||||
public GeyserDefineCommandsEventImpl(Map<String, Command> commands) {
|
||||
this.commands = commands;
|
||||
public GeyserDefineCommandsEventImpl(Map<String, ? extends Command> commands) {
|
||||
this.commands = Collections.unmodifiableMap(commands);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Map<String, Command> commands() {
|
||||
return Collections.unmodifiableMap(this.commands);
|
||||
return this.commands;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,9 +43,9 @@ import java.util.regex.Pattern;
|
|||
public record GeyserExtensionDescription(@NonNull String id,
|
||||
@NonNull String name,
|
||||
@NonNull String main,
|
||||
int humanApiVersion,
|
||||
int majorApiVersion,
|
||||
int minorApiVersion,
|
||||
int patchApiVersion,
|
||||
@NonNull String version,
|
||||
@NonNull List<String> authors) implements ExtensionDescription {
|
||||
|
||||
|
@ -82,9 +82,9 @@ public record GeyserExtensionDescription(@NonNull String id,
|
|||
throw new InvalidDescriptionException(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_format", name, apiVersion));
|
||||
}
|
||||
String[] api = apiVersion.split("\\.");
|
||||
int majorApi = Integer.parseUnsignedInt(api[0]);
|
||||
int minorApi = Integer.parseUnsignedInt(api[1]);
|
||||
int patchApi = Integer.parseUnsignedInt(api[2]);
|
||||
int humanApi = Integer.parseUnsignedInt(api[0]);
|
||||
int majorApi = Integer.parseUnsignedInt(api[1]);
|
||||
int minorApi = Integer.parseUnsignedInt(api[2]);
|
||||
|
||||
List<String> authors = new ArrayList<>();
|
||||
if (source.author != null) {
|
||||
|
@ -94,7 +94,7 @@ public record GeyserExtensionDescription(@NonNull String id,
|
|||
authors.addAll(source.authors);
|
||||
}
|
||||
|
||||
return new GeyserExtensionDescription(id, name, main, majorApi, minorApi, patchApi, version, authors);
|
||||
return new GeyserExtensionDescription(id, name, main, humanApi, majorApi, minorApi, version, authors);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
|
|
|
@ -29,10 +29,15 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
|
|||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.api.Geyser;
|
||||
import org.geysermc.api.util.ApiVersion;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.GeyserApi;
|
||||
import org.geysermc.geyser.api.event.ExtensionEventBus;
|
||||
import org.geysermc.geyser.api.extension.*;
|
||||
import org.geysermc.geyser.api.extension.Extension;
|
||||
import org.geysermc.geyser.api.extension.ExtensionDescription;
|
||||
import org.geysermc.geyser.api.extension.ExtensionLoader;
|
||||
import org.geysermc.geyser.api.extension.ExtensionLogger;
|
||||
import org.geysermc.geyser.api.extension.ExtensionManager;
|
||||
import org.geysermc.geyser.api.extension.exception.InvalidDescriptionException;
|
||||
import org.geysermc.geyser.api.extension.exception.InvalidExtensionException;
|
||||
import org.geysermc.geyser.extension.event.GeyserExtensionEventBus;
|
||||
|
@ -40,7 +45,12 @@ import org.geysermc.geyser.text.GeyserLocale;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.nio.file.*;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
|
@ -176,16 +186,22 @@ public class GeyserExtensionLoader extends ExtensionLoader {
|
|||
return;
|
||||
}
|
||||
|
||||
// Completely different API version
|
||||
if (description.majorApiVersion() != Geyser.api().majorApiVersion()) {
|
||||
GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_version", name, description.apiVersion()));
|
||||
return;
|
||||
}
|
||||
// Check whether an extensions' requested api version is compatible
|
||||
ApiVersion.Compatibility compatibility = GeyserApi.api().geyserApiVersion().supportsRequestedVersion(
|
||||
description.humanApiVersion(),
|
||||
description.majorApiVersion(),
|
||||
description.minorApiVersion()
|
||||
);
|
||||
|
||||
// If the extension requires new API features, being backwards compatible
|
||||
if (description.minorApiVersion() > Geyser.api().minorApiVersion()) {
|
||||
GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_version", name, description.apiVersion()));
|
||||
return;
|
||||
if (compatibility != ApiVersion.Compatibility.COMPATIBLE) {
|
||||
// Workaround for the switch to the Geyser API version instead of the Base API version in extensions
|
||||
if (compatibility == ApiVersion.Compatibility.HUMAN_DIFFER && description.humanApiVersion() == 1) {
|
||||
GeyserImpl.getInstance().getLogger().warning("The extension %s requested the Base API version %s, which is deprecated in favor of specifying the Geyser API version. Please update the extension, or contact its developer."
|
||||
.formatted(name, description.apiVersion()));
|
||||
} else {
|
||||
GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_version", name, description.apiVersion()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
GeyserExtensionContainer container = this.loadExtension(path, description);
|
||||
|
|
|
@ -25,19 +25,208 @@
|
|||
|
||||
package org.geysermc.geyser.extension.command;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.geyser.api.command.Command;
|
||||
import org.geysermc.geyser.api.command.CommandExecutor;
|
||||
import org.geysermc.geyser.api.command.CommandSource;
|
||||
import org.geysermc.geyser.api.connection.GeyserConnection;
|
||||
import org.geysermc.geyser.api.extension.Extension;
|
||||
import org.geysermc.geyser.api.util.TriState;
|
||||
import org.geysermc.geyser.command.GeyserCommand;
|
||||
import org.geysermc.geyser.command.GeyserCommandSource;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.incendo.cloud.CommandManager;
|
||||
import org.incendo.cloud.context.CommandContext;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.incendo.cloud.parser.standard.StringParser.greedyStringParser;
|
||||
|
||||
public abstract class GeyserExtensionCommand extends GeyserCommand {
|
||||
|
||||
private final Extension extension;
|
||||
private final String rootCommand;
|
||||
|
||||
public GeyserExtensionCommand(Extension extension, String name, String description, String permission) {
|
||||
super(name, description, permission);
|
||||
public GeyserExtensionCommand(@NonNull Extension extension, @NonNull String name, @NonNull String description,
|
||||
@NonNull String permission, @Nullable TriState permissionDefault,
|
||||
boolean playerOnly, boolean bedrockOnly) {
|
||||
|
||||
super(name, description, permission, permissionDefault, playerOnly, bedrockOnly);
|
||||
this.extension = extension;
|
||||
this.rootCommand = Objects.requireNonNull(extension.rootCommand());
|
||||
|
||||
if (this.rootCommand.isBlank()) {
|
||||
throw new IllegalStateException("rootCommand of extension " + extension.name() + " may not be blank");
|
||||
}
|
||||
}
|
||||
|
||||
public Extension extension() {
|
||||
public final Extension extension() {
|
||||
return this.extension;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String rootCommand() {
|
||||
return this.rootCommand;
|
||||
}
|
||||
|
||||
public static class Builder<T extends CommandSource> implements Command.Builder<T> {
|
||||
@NonNull private final Extension extension;
|
||||
@Nullable private Class<? extends T> sourceType;
|
||||
@Nullable private String name;
|
||||
@NonNull private String description = "";
|
||||
@NonNull private String permission = "";
|
||||
@Nullable private TriState permissionDefault;
|
||||
@Nullable private List<String> aliases;
|
||||
private boolean suggestedOpOnly = false; // deprecated for removal
|
||||
private boolean playerOnly = false;
|
||||
private boolean bedrockOnly = false;
|
||||
@Nullable private CommandExecutor<T> executor;
|
||||
|
||||
public Builder(@NonNull Extension extension) {
|
||||
this.extension = Objects.requireNonNull(extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Command.Builder<T> source(@NonNull Class<? extends T> sourceType) {
|
||||
this.sourceType = Objects.requireNonNull(sourceType, "command source type");
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder<T> name(@NonNull String name) {
|
||||
this.name = Objects.requireNonNull(name, "command name");
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder<T> description(@NonNull String description) {
|
||||
this.description = Objects.requireNonNull(description, "command description");
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder<T> permission(@NonNull String permission) {
|
||||
this.permission = Objects.requireNonNull(permission, "command permission");
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder<T> permission(@NonNull String permission, @NonNull TriState defaultValue) {
|
||||
this.permission = Objects.requireNonNull(permission, "command permission");
|
||||
this.permissionDefault = Objects.requireNonNull(defaultValue, "command permission defaultValue");
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder<T> aliases(@NonNull List<String> aliases) {
|
||||
this.aliases = Objects.requireNonNull(aliases, "command aliases");
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("removal") // this is our doing
|
||||
@Override
|
||||
public Builder<T> suggestedOpOnly(boolean suggestedOpOnly) {
|
||||
this.suggestedOpOnly = suggestedOpOnly;
|
||||
if (suggestedOpOnly) {
|
||||
// the most amount of legacy/deprecated behaviour I'm willing to support
|
||||
this.permissionDefault = TriState.NOT_SET;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("removal") // this is our doing
|
||||
@Override
|
||||
public Builder<T> executableOnConsole(boolean executableOnConsole) {
|
||||
this.playerOnly = !executableOnConsole;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Command.Builder<T> playerOnly(boolean playerOnly) {
|
||||
this.playerOnly = playerOnly;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder<T> bedrockOnly(boolean bedrockOnly) {
|
||||
this.bedrockOnly = bedrockOnly;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder<T> executor(@NonNull CommandExecutor<T> executor) {
|
||||
this.executor = Objects.requireNonNull(executor, "command executor");
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public GeyserExtensionCommand build() {
|
||||
// These are captured in the anonymous lambda below and shouldn't change even if the builder does
|
||||
final Class<? extends T> sourceType = this.sourceType;
|
||||
final boolean suggestedOpOnly = this.suggestedOpOnly;
|
||||
final CommandExecutor<T> executor = this.executor;
|
||||
|
||||
if (name == null) {
|
||||
throw new IllegalArgumentException("name was not provided for a command in extension " + extension.name());
|
||||
}
|
||||
if (sourceType == null) {
|
||||
throw new IllegalArgumentException("Source type was not defined for command " + name + " in extension " + extension.name());
|
||||
}
|
||||
if (executor == null) {
|
||||
throw new IllegalArgumentException("Command executor was not defined for command " + name + " in extension " + extension.name());
|
||||
}
|
||||
|
||||
// if the source type is a GeyserConnection then it is inherently bedrockOnly
|
||||
final boolean bedrockOnly = this.bedrockOnly || GeyserConnection.class.isAssignableFrom(sourceType);
|
||||
// a similar check would exist for executableOnConsole, but there is not a logger type exposed in the api
|
||||
|
||||
GeyserExtensionCommand command = new GeyserExtensionCommand(extension, name, description, permission, permissionDefault, playerOnly, bedrockOnly) {
|
||||
|
||||
@Override
|
||||
public void register(CommandManager<GeyserCommandSource> manager) {
|
||||
manager.command(baseBuilder(manager)
|
||||
.optional("args", greedyStringParser())
|
||||
.handler(this::execute));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void execute(CommandContext<GeyserCommandSource> context) {
|
||||
GeyserCommandSource source = context.sender();
|
||||
String[] args = context.getOrDefault("args", "").split(" ");
|
||||
|
||||
if (sourceType.isInstance(source)) {
|
||||
executor.execute((T) source, this, args);
|
||||
return;
|
||||
}
|
||||
|
||||
@Nullable GeyserSession session = source.connection();
|
||||
if (sourceType.isInstance(session)) {
|
||||
executor.execute((T) session, this, args);
|
||||
return;
|
||||
}
|
||||
|
||||
// currently, the only subclass of CommandSource exposed in the api is GeyserConnection.
|
||||
// when this command was registered, we enabled bedrockOnly if the sourceType was a GeyserConnection.
|
||||
// as a result, the permission checker should handle that case and this method shouldn't even be reached.
|
||||
source.sendMessage("You must be a " + sourceType.getSimpleName() + " to run this command.");
|
||||
}
|
||||
|
||||
@SuppressWarnings("removal") // this is our doing
|
||||
@Override
|
||||
public boolean isSuggestedOpOnly() {
|
||||
return suggestedOpOnly;
|
||||
}
|
||||
};
|
||||
|
||||
if (aliases != null) {
|
||||
command.aliases = new ArrayList<>(aliases);
|
||||
}
|
||||
return command;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -82,11 +82,15 @@ public class GeyserAdvancement {
|
|||
this.rootId = this.advancement.getId();
|
||||
} else {
|
||||
// Go through our cache, and descend until we find the root ID
|
||||
GeyserAdvancement advancement = advancementsCache.getStoredAdvancements().get(this.advancement.getParentId());
|
||||
if (advancement.getParentId() == null) {
|
||||
this.rootId = advancement.getId();
|
||||
GeyserAdvancement parent = advancementsCache.getStoredAdvancements().get(this.advancement.getParentId());
|
||||
if (parent == null) {
|
||||
// Parent doesn't exist, is invalid, or couldn't be found for another reason
|
||||
// So assuming there is no parent and this is the root
|
||||
this.rootId = this.advancement.getId();
|
||||
} else if (parent.getParentId() == null) {
|
||||
this.rootId = parent.getId();
|
||||
} else {
|
||||
this.rootId = advancement.getRootId(advancementsCache);
|
||||
this.rootId = parent.getRootId(advancementsCache);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -118,11 +118,6 @@ public class GeyserWorldManager extends WorldManager {
|
|||
return GameMode.SURVIVAL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(GeyserSession session, String permission) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public CompletableFuture<@Nullable DataComponents> getPickItemComponents(GeyserSession session, int x, int y, int z, boolean addNbtData) {
|
||||
|
|
|
@ -185,15 +185,6 @@ public abstract class WorldManager {
|
|||
session.sendCommand("difficulty " + difficulty.name().toLowerCase(Locale.ROOT));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given session's player has a permission
|
||||
*
|
||||
* @param session The session of the player to check the permission of
|
||||
* @param permission The permission node to check
|
||||
* @return True if the player has the requested permission, false if not
|
||||
*/
|
||||
public abstract boolean hasPermission(GeyserSession session, String permission);
|
||||
|
||||
/**
|
||||
* Returns a list of biome identifiers available on the server.
|
||||
*/
|
||||
|
|
|
@ -42,8 +42,8 @@ import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData;
|
|||
import org.geysermc.geyser.api.pack.PathPackCodec;
|
||||
import org.geysermc.geyser.impl.camera.GeyserCameraFade;
|
||||
import org.geysermc.geyser.impl.camera.GeyserCameraPosition;
|
||||
import org.geysermc.geyser.command.GeyserCommandManager;
|
||||
import org.geysermc.geyser.event.GeyserEventRegistrar;
|
||||
import org.geysermc.geyser.extension.command.GeyserExtensionCommand;
|
||||
import org.geysermc.geyser.item.GeyserCustomItemData;
|
||||
import org.geysermc.geyser.item.GeyserCustomItemOptions;
|
||||
import org.geysermc.geyser.item.GeyserNonVanillaCustomItemData;
|
||||
|
@ -67,7 +67,7 @@ public class ProviderRegistryLoader implements RegistryLoader<Map<Class<?>, Prov
|
|||
@Override
|
||||
public Map<Class<?>, ProviderSupplier> load(Map<Class<?>, ProviderSupplier> providers) {
|
||||
// misc
|
||||
providers.put(Command.Builder.class, args -> new GeyserCommandManager.CommandBuilder<>((Extension) args[0]));
|
||||
providers.put(Command.Builder.class, args -> new GeyserExtensionCommand.Builder<>((Extension) args[0]));
|
||||
|
||||
providers.put(CustomBlockComponents.Builder.class, args -> new GeyserCustomBlockComponents.Builder());
|
||||
providers.put(CustomBlockData.Builder.class, args -> new GeyserCustomBlockData.Builder());
|
||||
|
|
|
@ -54,6 +54,8 @@ import org.cloudburstmc.math.vector.Vector3d;
|
|||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.math.vector.Vector3i;
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.cloudburstmc.netty.channel.raknet.RakChildChannel;
|
||||
import org.cloudburstmc.netty.handler.codec.raknet.common.RakSessionCodec;
|
||||
import org.cloudburstmc.protocol.bedrock.BedrockDisconnectReasons;
|
||||
import org.cloudburstmc.protocol.bedrock.BedrockServerSession;
|
||||
import org.cloudburstmc.protocol.bedrock.data.Ability;
|
||||
|
@ -1454,11 +1456,28 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID playerUuid() {
|
||||
return javaUuid(); // CommandSource allows nullable
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeyserSession connection() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String locale() {
|
||||
return clientData.getLanguageCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(String permission) {
|
||||
// for Geyser-Standalone, standalone's permission system will handle it.
|
||||
// for server platforms, the session will be mapped to a server command sender, and the server's api will be used.
|
||||
return geyser.commandRegistry().hasPermission(this, permission);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a chat message to the Java server.
|
||||
*/
|
||||
|
@ -1771,17 +1790,6 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||
upstream.sendPacket(gameRulesChangedPacket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given session's player has a permission
|
||||
*
|
||||
* @param permission The permission node to check
|
||||
* @return true if the player has the requested permission, false if not
|
||||
*/
|
||||
@Override
|
||||
public boolean hasPermission(String permission) {
|
||||
return geyser.getWorldManager().hasPermission(this, permission);
|
||||
}
|
||||
|
||||
private static final Ability[] USED_ABILITIES = Ability.values();
|
||||
|
||||
/**
|
||||
|
@ -2092,6 +2100,12 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||
return this.cameraData.fogEffects();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int ping() {
|
||||
RakSessionCodec rakSessionCodec = ((RakChildChannel) getUpstream().getSession().getPeer().getChannel()).rakPipeline().get(RakSessionCodec.class);
|
||||
return (int) Math.floor(rakSessionCodec.getPing());
|
||||
}
|
||||
|
||||
public void addCommandEnum(String name, String enums) {
|
||||
softEnumPacket(name, SoftEnumUpdateType.ADD, enums);
|
||||
}
|
||||
|
|
|
@ -158,7 +158,15 @@ public class AdvancementsCache {
|
|||
// Cache language for easier access
|
||||
String language = session.locale();
|
||||
|
||||
String earned = isEarned(advancement) ? "yes" : "no";
|
||||
boolean advancementHasProgress = advancement.getRequirements().size() > 1;
|
||||
|
||||
int advancementProgress = getProgress(advancement);
|
||||
int advancementRequirements = advancement.getRequirements().size();
|
||||
|
||||
boolean advancementEarned = advancementRequirements > 0
|
||||
&& advancementProgress >= advancementRequirements;
|
||||
|
||||
String earned = advancementEarned ? "yes" : "no";
|
||||
|
||||
String description = getColorFromAdvancementFrameType(advancement) + MessageTranslator.convertMessage(advancement.getDisplayData().getDescription(), language);
|
||||
String earnedString = GeyserLocale.getPlayerLocaleString("geyser.advancements.earned", language, MinecraftLocale.getLocaleString("gui." + earned, language));
|
||||
|
@ -171,10 +179,20 @@ public class AdvancementsCache {
|
|||
(Description) Mine stone with your new pickaxe
|
||||
|
||||
Earned: Yes
|
||||
Progress: 1/4 // When advancement has multiple requirements
|
||||
Parent Advancement: Minecraft // If relevant
|
||||
*/
|
||||
|
||||
String content = description + "\n\n§f" + earnedString + "\n";
|
||||
|
||||
if (advancementHasProgress) {
|
||||
// Only display progress with multiple requirements
|
||||
String progress = MinecraftLocale.getLocaleString("advancements.progress", language)
|
||||
.replaceFirst("%s", String.valueOf(advancementProgress))
|
||||
.replaceFirst("%s", String.valueOf(advancementRequirements));
|
||||
content += GeyserLocale.getPlayerLocaleString("geyser.advancements.progress", language, progress) + "\n";
|
||||
}
|
||||
|
||||
if (!currentAdvancementCategoryId.equals(advancement.getParentId())) {
|
||||
// Only display the parent if it is not the category
|
||||
content += GeyserLocale.getPlayerLocaleString("geyser.advancements.parentid", language, MessageTranslator.convertMessage(storedAdvancements.get(advancement.getParentId()).getDisplayData().getTitle(), language));
|
||||
|
@ -200,34 +218,44 @@ public class AdvancementsCache {
|
|||
* @return true if the advancement has been earned.
|
||||
*/
|
||||
public boolean isEarned(GeyserAdvancement advancement) {
|
||||
boolean earned = false;
|
||||
if (advancement.getRequirements().size() == 0) {
|
||||
if (advancement.getRequirements().isEmpty()) {
|
||||
// Minecraft handles this case, so we better as well
|
||||
return false;
|
||||
}
|
||||
Map<String, Long> progress = storedAdvancementProgress.get(advancement.getId());
|
||||
if (progress != null) {
|
||||
// Progress should never be above requirements count, but you never know
|
||||
return getProgress(advancement) >= advancement.getRequirements().size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the progress on an advancement.
|
||||
*
|
||||
* @param advancement the advancement to determine
|
||||
* @return the progress on the advancement.
|
||||
*/
|
||||
public int getProgress(GeyserAdvancement advancement) {
|
||||
if (advancement.getRequirements().isEmpty()) {
|
||||
// Minecraft handles this case
|
||||
return 0;
|
||||
}
|
||||
int progress = 0;
|
||||
Map<String, Long> progressMap = storedAdvancementProgress.get(advancement.getId());
|
||||
if (progressMap != null) {
|
||||
// Each advancement's requirement must be fulfilled
|
||||
// For example, [[zombie, blaze, skeleton]] means that one of those three categories must be achieved
|
||||
// But [[zombie], [blaze], [skeleton]] means that all three requirements must be completed
|
||||
for (List<String> requirements : advancement.getRequirements()) {
|
||||
boolean requirementsDone = false;
|
||||
for (String requirement : requirements) {
|
||||
Long obtained = progress.get(requirement);
|
||||
Long obtained = progressMap.get(requirement);
|
||||
// -1 means that this particular component required for completing the advancement
|
||||
// has yet to be fulfilled
|
||||
if (obtained != null && !obtained.equals(-1L)) {
|
||||
requirementsDone = true;
|
||||
break;
|
||||
progress++;
|
||||
}
|
||||
}
|
||||
if (!requirementsDone) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
earned = true;
|
||||
}
|
||||
return earned;
|
||||
|
||||
return progress;
|
||||
}
|
||||
|
||||
public String getColorFromAdvancementFrameType(GeyserAdvancement advancement) {
|
||||
|
|
|
@ -28,6 +28,7 @@ package org.geysermc.geyser.translator.protocol.bedrock;
|
|||
import org.cloudburstmc.protocol.bedrock.packet.CommandRequestPacket;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.command.CommandRegistry;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.Translator;
|
||||
|
@ -43,13 +44,26 @@ public class BedrockCommandRequestTranslator extends PacketTranslator<CommandReq
|
|||
}
|
||||
|
||||
static void handleCommand(GeyserSession session, String command) {
|
||||
if (!(session.getGeyser().getPlatformType() == PlatformType.STANDALONE
|
||||
&& GeyserImpl.getInstance().commandManager().runCommand(session, command))) {
|
||||
if (MessageTranslator.isTooLong(command, session)) {
|
||||
return;
|
||||
}
|
||||
if (session.getGeyser().getPlatformType() == PlatformType.STANDALONE ||
|
||||
session.getGeyser().getPlatformType() == PlatformType.VIAPROXY) {
|
||||
// try to handle the command within the standalone/viaproxy command manager
|
||||
|
||||
session.sendCommand(command);
|
||||
String[] args = command.split(" ");
|
||||
if (args.length > 0) {
|
||||
String root = args[0];
|
||||
|
||||
CommandRegistry registry = GeyserImpl.getInstance().commandRegistry();
|
||||
if (registry.rootCommands().contains(root)) {
|
||||
registry.runCommand(session, command);
|
||||
return; // don't pass the command to the java server
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (MessageTranslator.isTooLong(command, session)) {
|
||||
return;
|
||||
}
|
||||
|
||||
session.sendCommand(command);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ package org.geysermc.geyser.translator.protocol.bedrock.entity.player;
|
|||
|
||||
import org.cloudburstmc.protocol.bedrock.packet.SetDefaultGameTypePacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.SetPlayerGameTypePacket;
|
||||
import org.geysermc.geyser.Permissions;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.Translator;
|
||||
|
@ -41,7 +42,7 @@ public class BedrockSetDefaultGameTypeTranslator extends PacketTranslator<SetDef
|
|||
*/
|
||||
@Override
|
||||
public void translate(GeyserSession session, SetDefaultGameTypePacket packet) {
|
||||
if (session.getOpPermissionLevel() >= 2 && session.hasPermission("geyser.settings.server")) {
|
||||
if (session.getOpPermissionLevel() >= 2 && session.hasPermission(Permissions.SERVER_SETTINGS)) {
|
||||
session.getGeyser().getWorldManager().setDefaultGameMode(session, GameMode.byId(packet.getGamemode()));
|
||||
}
|
||||
// Stop the client from updating their own Gamemode without telling the server
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
|
||||
package org.geysermc.geyser.translator.protocol.bedrock.entity.player;
|
||||
|
||||
import org.geysermc.geyser.Permissions;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.setting.Difficulty;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.SetDifficultyPacket;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
@ -39,7 +40,7 @@ public class BedrockSetDifficultyTranslator extends PacketTranslator<SetDifficul
|
|||
*/
|
||||
@Override
|
||||
public void translate(GeyserSession session, SetDifficultyPacket packet) {
|
||||
if (session.getOpPermissionLevel() >= 2 && session.hasPermission("geyser.settings.server")) {
|
||||
if (session.getOpPermissionLevel() >= 2 && session.hasPermission(Permissions.SERVER_SETTINGS)) {
|
||||
if (packet.getDifficulty() != session.getWorldCache().getDifficulty().ordinal()) {
|
||||
session.getGeyser().getWorldManager().setDifficulty(session, Difficulty.from(packet.getDifficulty()));
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
package org.geysermc.geyser.translator.protocol.bedrock.entity.player;
|
||||
|
||||
import org.cloudburstmc.protocol.bedrock.packet.SetPlayerGameTypePacket;
|
||||
import org.geysermc.geyser.Permissions;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.Translator;
|
||||
|
@ -45,7 +46,7 @@ public class BedrockSetPlayerGameTypeTranslator extends PacketTranslator<SetPlay
|
|||
@Override
|
||||
public void translate(GeyserSession session, SetPlayerGameTypePacket packet) {
|
||||
// yes, if you are OP
|
||||
if (session.getOpPermissionLevel() >= 2 && session.hasPermission("geyser.settings.server")) {
|
||||
if (session.getOpPermissionLevel() >= 2 && session.hasPermission(Permissions.SERVER_SETTINGS)) {
|
||||
if (packet.getGamemode() != session.getGameMode().ordinal()) {
|
||||
// Bedrock has more Gamemodes than Java, leading to cases 5 (for "default") and 6 (for "spectator") being sent
|
||||
// https://github.com/CloudburstMC/Protocol/blob/3.0/bedrock-codec/src/main/java/org/cloudburstmc/protocol/bedrock/data/GameType.java
|
||||
|
|
|
@ -41,7 +41,7 @@ import org.cloudburstmc.protocol.bedrock.data.command.*;
|
|||
import org.cloudburstmc.protocol.bedrock.packet.AvailableCommandsPacket;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.event.java.ServerDefineCommandsEvent;
|
||||
import org.geysermc.geyser.command.GeyserCommandManager;
|
||||
import org.geysermc.geyser.command.CommandRegistry;
|
||||
import org.geysermc.geyser.item.enchantment.Enchantment;
|
||||
import org.geysermc.geyser.registry.BlockRegistries;
|
||||
import org.geysermc.geyser.registry.Registries;
|
||||
|
@ -122,7 +122,7 @@ public class JavaCommandsTranslator extends PacketTranslator<ClientboundCommands
|
|||
return;
|
||||
}
|
||||
|
||||
GeyserCommandManager manager = session.getGeyser().commandManager();
|
||||
CommandRegistry registry = session.getGeyser().commandRegistry();
|
||||
CommandNode[] nodes = packet.getNodes();
|
||||
List<CommandData> commandData = new ArrayList<>();
|
||||
IntSet commandNodes = new IntOpenHashSet();
|
||||
|
@ -151,8 +151,10 @@ public class JavaCommandsTranslator extends PacketTranslator<ClientboundCommands
|
|||
CommandOverloadData[] params = getParams(session, nodes[nodeIndex], nodes);
|
||||
|
||||
// Insert the alias name into the command list
|
||||
commands.computeIfAbsent(new BedrockCommandInfo(node.getName().toLowerCase(Locale.ROOT), manager.description(node.getName().toLowerCase(Locale.ROOT)), params),
|
||||
index -> new HashSet<>()).add(node.getName().toLowerCase());
|
||||
String name = node.getName().toLowerCase(Locale.ROOT);
|
||||
String description = registry.description(name, session.locale());
|
||||
BedrockCommandInfo info = new BedrockCommandInfo(name, description, params);
|
||||
commands.computeIfAbsent(info, $ -> new HashSet<>()).add(name);
|
||||
}
|
||||
|
||||
var eventBus = session.getGeyser().eventBus();
|
||||
|
@ -169,8 +171,8 @@ public class JavaCommandsTranslator extends PacketTranslator<ClientboundCommands
|
|||
return;
|
||||
}
|
||||
|
||||
// The command flags, not sure what these do apart from break things
|
||||
Set<CommandData.Flag> flags = Set.of();
|
||||
// The command flags, set to NOT_CHEAT so known commands can be used while achievements are enabled.
|
||||
Set<CommandData.Flag> flags = Set.of(CommandData.Flag.NOT_CHEAT);
|
||||
|
||||
// Loop through all the found commands
|
||||
for (Map.Entry<BedrockCommandInfo, Set<String>> entry : commands.entrySet()) {
|
||||
|
@ -449,7 +451,7 @@ public class JavaCommandsTranslator extends PacketTranslator<ClientboundCommands
|
|||
type = (CommandParam) mappedType;
|
||||
// Bedrock throws a fit if an optional message comes after a string or target
|
||||
// Example vanilla commands: ban-ip, ban, and kick
|
||||
if (optional && type == CommandParam.MESSAGE && (paramData.getType() == CommandParam.STRING || paramData.getType() == CommandParam.TARGET)) {
|
||||
if (optional && type == CommandParam.MESSAGE && paramData != null && (paramData.getType() == CommandParam.STRING || paramData.getType() == CommandParam.TARGET)) {
|
||||
optional = false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,6 +49,8 @@ import org.geysermc.geyser.inventory.recipe.GeyserShapedRecipe;
|
|||
import org.geysermc.geyser.inventory.recipe.GeyserShapelessRecipe;
|
||||
import org.geysermc.geyser.inventory.recipe.GeyserStonecutterData;
|
||||
import org.geysermc.geyser.inventory.recipe.TrimRecipe;
|
||||
import org.geysermc.geyser.item.type.BedrockRequiresTagItem;
|
||||
import org.geysermc.geyser.item.type.Item;
|
||||
import org.geysermc.geyser.registry.Registries;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
@ -443,13 +445,18 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator<ClientboundUpd
|
|||
}
|
||||
|
||||
List<String> translateShulkerBoxRecipe(GeyserShapelessRecipe recipe) {
|
||||
ItemData output = ItemTranslator.translateToBedrock(session, recipe.result());
|
||||
ItemStack result = recipe.result();
|
||||
ItemData output = ItemTranslator.translateToBedrock(session, result);
|
||||
if (!output.isValid()) {
|
||||
// Likely modded item that Bedrock will complain about if it persists
|
||||
return null;
|
||||
}
|
||||
// Strip NBT - tools won't appear in the recipe book otherwise
|
||||
// output = output.toBuilder().tag(null).build(); // TODO confirm???
|
||||
|
||||
Item javaItem = Registries.JAVA_ITEMS.get(result.getId());
|
||||
if (!(javaItem instanceof BedrockRequiresTagItem)) {
|
||||
// Strip NBT - tools won't appear in the recipe book otherwise
|
||||
output = output.toBuilder().tag(null).build();
|
||||
}
|
||||
ItemDescriptorWithCount[][] inputCombinations = combinations(session, recipe.ingredients());
|
||||
if (inputCombinations == null) {
|
||||
return null;
|
||||
|
@ -467,13 +474,18 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator<ClientboundUpd
|
|||
}
|
||||
|
||||
List<String> translateShapelessRecipe(GeyserShapelessRecipe recipe) {
|
||||
ItemData output = ItemTranslator.translateToBedrock(session, recipe.result());
|
||||
ItemStack result = recipe.result();
|
||||
ItemData output = ItemTranslator.translateToBedrock(session, result);
|
||||
if (!output.isValid()) {
|
||||
// Likely modded item that Bedrock will complain about if it persists
|
||||
return null;
|
||||
}
|
||||
// Strip NBT - tools won't appear in the recipe book otherwise
|
||||
//output = output.toBuilder().tag(null).build(); // TODO confirm this is still true???
|
||||
|
||||
Item javaItem = Registries.JAVA_ITEMS.get(result.getId());
|
||||
if (!(javaItem instanceof BedrockRequiresTagItem)) {
|
||||
// Strip NBT - tools won't appear in the recipe book otherwise
|
||||
output = output.toBuilder().tag(null).build();
|
||||
}
|
||||
ItemDescriptorWithCount[][] inputCombinations = combinations(session, recipe.ingredients());
|
||||
if (inputCombinations == null) {
|
||||
return null;
|
||||
|
@ -491,13 +503,18 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator<ClientboundUpd
|
|||
}
|
||||
|
||||
List<String> translateShapedRecipe(GeyserShapedRecipe recipe) {
|
||||
ItemData output = ItemTranslator.translateToBedrock(session, recipe.result());
|
||||
ItemStack result = recipe.result();
|
||||
ItemData output = ItemTranslator.translateToBedrock(session, result);
|
||||
if (!output.isValid()) {
|
||||
// Likely modded item that Bedrock will complain about if it persists
|
||||
return null;
|
||||
}
|
||||
// See above
|
||||
//output = output.toBuilder().tag(null).build();
|
||||
|
||||
Item javaItem = Registries.JAVA_ITEMS.get(result.getId());
|
||||
if (!(javaItem instanceof BedrockRequiresTagItem)) {
|
||||
// Strip NBT - tools won't appear in the recipe book otherwise
|
||||
output = output.toBuilder().tag(null).build();
|
||||
}
|
||||
ItemDescriptorWithCount[][] inputCombinations = combinations(session, recipe.ingredients());
|
||||
if (inputCombinations == null) {
|
||||
return null;
|
||||
|
|
|
@ -100,6 +100,18 @@ public class FileUtils {
|
|||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the specified file or copy if from resources
|
||||
*
|
||||
* @param file File to open
|
||||
* @param name Name of the resource get if needed
|
||||
* @return File handle of the specified file
|
||||
* @throws IOException if the file failed to copy from resource
|
||||
*/
|
||||
public static File fileOrCopiedFromResource(File file, String name, GeyserBootstrap bootstrap) throws IOException {
|
||||
return fileOrCopiedFromResource(file, name, Function.identity(), bootstrap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the given data to the specified file on disk
|
||||
*
|
||||
|
|
|
@ -29,6 +29,7 @@ import org.cloudburstmc.protocol.bedrock.packet.SetDifficultyPacket;
|
|||
import org.geysermc.cumulus.component.DropdownComponent;
|
||||
import org.geysermc.cumulus.form.CustomForm;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.Permissions;
|
||||
import org.geysermc.geyser.level.GameRule;
|
||||
import org.geysermc.geyser.level.WorldManager;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
@ -81,7 +82,7 @@ public class SettingsUtils {
|
|||
}
|
||||
}
|
||||
|
||||
boolean showGamerules = session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.gamerules");
|
||||
boolean showGamerules = session.getOpPermissionLevel() >= 2 || session.hasPermission(Permissions.SETTINGS_GAMERULES);
|
||||
if (showGamerules) {
|
||||
builder.label("geyser.settings.title.game_rules")
|
||||
.translator(MinecraftLocale::getLocaleString); // we need translate gamerules next
|
||||
|
|
|
@ -168,7 +168,6 @@ above-bedrock-nether-building: false
|
|||
force-resource-packs: true
|
||||
|
||||
# Allows Xbox achievements to be unlocked.
|
||||
# THIS DISABLES ALL COMMANDS FROM SUCCESSFULLY RUNNING FOR BEDROCK IN-GAME, as otherwise Bedrock thinks you are cheating.
|
||||
xbox-achievements-enabled: false
|
||||
|
||||
# Whether player IP addresses will be logged by the server.
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit afbf78bbe0b39d0a076a42c228828c12f7f7da90
|
||||
Subproject commit 60b20023a92f084aba895ab0336e70fa7fb311fb
|
9
core/src/main/resources/permissions.yml
Normal file
9
core/src/main/resources/permissions.yml
Normal file
|
@ -0,0 +1,9 @@
|
|||
|
||||
# Add any permissions here that all players should have.
|
||||
# Permissions for builtin Geyser commands do not have to be listed here.
|
||||
|
||||
# If an extension/plugin registers their permissions with default values, entries here are typically unnecessary.
|
||||
# If extensions don't register their permissions, permissions that everyone should have must be added here manually.
|
||||
|
||||
default-permissions:
|
||||
- geyser.command.help # this is unnecessary
|
Loading…
Add table
Add a link
Reference in a new issue