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
				
			
		| 
						 | 
				
			
			@ -1,8 +1,24 @@
 | 
			
		|||
plugins {
 | 
			
		||||
    // Allow blossom to mark sources root of templates
 | 
			
		||||
    idea
 | 
			
		||||
    id("geyser.publish-conventions")
 | 
			
		||||
    alias(libs.plugins.blossom)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
    api(libs.base.api)
 | 
			
		||||
    api(libs.math)
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
version = property("version")!!
 | 
			
		||||
val apiVersion = (version as String).removeSuffix("-SNAPSHOT")
 | 
			
		||||
 | 
			
		||||
sourceSets {
 | 
			
		||||
    main {
 | 
			
		||||
        blossom {
 | 
			
		||||
            javaSources {
 | 
			
		||||
                property("version", apiVersion)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,53 @@
 | 
			
		|||
/*
 | 
			
		||||
 * 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.api;
 | 
			
		||||
 | 
			
		||||
import org.geysermc.api.util.ApiVersion;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Not a public API. For internal use only. May change without notice.
 | 
			
		||||
 * This class is processed before compilation to insert build properties.
 | 
			
		||||
 */
 | 
			
		||||
class BuildData {
 | 
			
		||||
    static final String VERSION = "{{ version }}";
 | 
			
		||||
    static final ApiVersion API_VERSION;
 | 
			
		||||
 | 
			
		||||
    static {
 | 
			
		||||
        String[] parts = VERSION.split("\\.");
 | 
			
		||||
        if (parts.length != 3) {
 | 
			
		||||
            throw new RuntimeException("Invalid api version: " + VERSION);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            int human = Integer.parseInt(parts[0]);
 | 
			
		||||
            int major = Integer.parseInt(parts[1]);
 | 
			
		||||
            int minor = Integer.parseInt(parts[2]);
 | 
			
		||||
            API_VERSION = new ApiVersion(human, major, minor);
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            throw new RuntimeException("Invalid api version: " + VERSION, e);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -29,6 +29,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
 | 
			
		|||
import org.checkerframework.checker.nullness.qual.Nullable;
 | 
			
		||||
import org.geysermc.api.Geyser;
 | 
			
		||||
import org.geysermc.api.GeyserApiBase;
 | 
			
		||||
import org.geysermc.api.util.ApiVersion;
 | 
			
		||||
import org.geysermc.geyser.api.command.CommandSource;
 | 
			
		||||
import org.geysermc.geyser.api.connection.GeyserConnection;
 | 
			
		||||
import org.geysermc.geyser.api.event.EventBus;
 | 
			
		||||
| 
						 | 
				
			
			@ -169,4 +170,14 @@ public interface GeyserApi extends GeyserApiBase {
 | 
			
		|||
    static GeyserApi api() {
 | 
			
		||||
        return Geyser.api(GeyserApi.class);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the {@link ApiVersion} representing the current Geyser api version.
 | 
			
		||||
     * See the <a href="https://github.com/geysermc/api/blob/master/geyser-versioning.md">Geyser version outline</a>)
 | 
			
		||||
     *
 | 
			
		||||
     * @return the current geyser api version
 | 
			
		||||
     */
 | 
			
		||||
     default ApiVersion geyserApiVersion() {
 | 
			
		||||
        return BuildData.API_VERSION;
 | 
			
		||||
     }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -28,7 +28,9 @@ package org.geysermc.geyser.api.command;
 | 
			
		|||
import org.checkerframework.checker.nullness.qual.NonNull;
 | 
			
		||||
import org.geysermc.geyser.api.GeyserApi;
 | 
			
		||||
import org.geysermc.geyser.api.connection.GeyserConnection;
 | 
			
		||||
import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent;
 | 
			
		||||
import org.geysermc.geyser.api.extension.Extension;
 | 
			
		||||
import org.geysermc.geyser.api.util.TriState;
 | 
			
		||||
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
| 
						 | 
				
			
			@ -58,15 +60,15 @@ public interface Command {
 | 
			
		|||
     * Gets the permission node associated with
 | 
			
		||||
     * this command.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the permission node for this command
 | 
			
		||||
     * @return the permission node for this command if defined, otherwise an empty string
 | 
			
		||||
     */
 | 
			
		||||
    @NonNull
 | 
			
		||||
    String permission();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the aliases for this command.
 | 
			
		||||
     * Gets the aliases for this command, as an unmodifiable list
 | 
			
		||||
     *
 | 
			
		||||
     * @return the aliases for this command
 | 
			
		||||
     * @return the aliases for this command as an unmodifiable list
 | 
			
		||||
     */
 | 
			
		||||
    @NonNull
 | 
			
		||||
    List<String> aliases();
 | 
			
		||||
| 
						 | 
				
			
			@ -75,35 +77,39 @@ public interface Command {
 | 
			
		|||
     * Gets if this command is designed to be used only by server operators.
 | 
			
		||||
     *
 | 
			
		||||
     * @return if this command is designated to be used only by server operators.
 | 
			
		||||
     * @deprecated this method is not guaranteed to provide meaningful or expected results.
 | 
			
		||||
     */
 | 
			
		||||
    boolean isSuggestedOpOnly();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets if this command is executable on console.
 | 
			
		||||
     *
 | 
			
		||||
     * @return if this command is executable on console
 | 
			
		||||
     */
 | 
			
		||||
    boolean isExecutableOnConsole();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the subcommands associated with this
 | 
			
		||||
     * command. Mainly used within the Geyser Standalone
 | 
			
		||||
     * GUI to know what subcommands are supported.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the subcommands associated with this command
 | 
			
		||||
     */
 | 
			
		||||
    @NonNull
 | 
			
		||||
    default List<String> subCommands() {
 | 
			
		||||
        return Collections.emptyList();
 | 
			
		||||
    @Deprecated(forRemoval = true)
 | 
			
		||||
    default boolean isSuggestedOpOnly() {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Used to send a deny message to Java players if this command can only be used by Bedrock players.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if this command can only be used by Bedrock players.
 | 
			
		||||
     * @return true if this command is executable on console
 | 
			
		||||
     * @deprecated use {@link #isPlayerOnly()} instead (inverted)
 | 
			
		||||
     */
 | 
			
		||||
    default boolean isBedrockOnly() {
 | 
			
		||||
        return false;
 | 
			
		||||
    @Deprecated(forRemoval = true)
 | 
			
		||||
    default boolean isExecutableOnConsole() {
 | 
			
		||||
        return !isPlayerOnly();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return true if this command can only be used by players
 | 
			
		||||
     */
 | 
			
		||||
    boolean isPlayerOnly();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return true if this command can only be used by Bedrock players
 | 
			
		||||
     */
 | 
			
		||||
    boolean isBedrockOnly();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @deprecated this method will always return an empty immutable list
 | 
			
		||||
     */
 | 
			
		||||
    @Deprecated(forRemoval = true)
 | 
			
		||||
    @NonNull
 | 
			
		||||
    default List<String> subCommands() {
 | 
			
		||||
        return Collections.emptyList();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
| 
						 | 
				
			
			@ -128,7 +134,7 @@ public interface Command {
 | 
			
		|||
         * is an instance of this source.
 | 
			
		||||
         *
 | 
			
		||||
         * @param sourceType the source type
 | 
			
		||||
         * @return the builder
 | 
			
		||||
         * @return this builder
 | 
			
		||||
         */
 | 
			
		||||
        Builder<T> source(@NonNull Class<? extends T> sourceType);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -136,7 +142,7 @@ public interface Command {
 | 
			
		|||
         * Sets the command name.
 | 
			
		||||
         *
 | 
			
		||||
         * @param name the command name
 | 
			
		||||
         * @return the builder
 | 
			
		||||
         * @return this builder
 | 
			
		||||
         */
 | 
			
		||||
        Builder<T> name(@NonNull String name);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -144,23 +150,40 @@ public interface Command {
 | 
			
		|||
         * Sets the command description.
 | 
			
		||||
         *
 | 
			
		||||
         * @param description the command description
 | 
			
		||||
         * @return the builder
 | 
			
		||||
         * @return this builder
 | 
			
		||||
         */
 | 
			
		||||
        Builder<T> description(@NonNull String description);
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Sets the permission node.
 | 
			
		||||
         * Sets the permission node required to run this command. <br>
 | 
			
		||||
         * It will not be registered with any permission registries, such as an underlying server,
 | 
			
		||||
         * or a permissions Extension (unlike {@link #permission(String, TriState)}).
 | 
			
		||||
         *
 | 
			
		||||
         * @param permission the permission node
 | 
			
		||||
         * @return the builder
 | 
			
		||||
         * @return this builder
 | 
			
		||||
         */
 | 
			
		||||
        Builder<T> permission(@NonNull String permission);
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Sets the permission node and its default value. The usage of the default value is platform dependant
 | 
			
		||||
         * and may or may not be used. For example, it may be registered to an underlying server.
 | 
			
		||||
         * <p>
 | 
			
		||||
         * Extensions may instead listen for {@link GeyserRegisterPermissionsEvent} to register permissions,
 | 
			
		||||
         * especially if the same permission is required by multiple commands. Also see this event for TriState meanings.
 | 
			
		||||
         *
 | 
			
		||||
         * @param permission the permission node
 | 
			
		||||
         * @param defaultValue the node's default value
 | 
			
		||||
         * @return this builder
 | 
			
		||||
         * @deprecated this method is experimental and may be removed in the future
 | 
			
		||||
         */
 | 
			
		||||
        @Deprecated
 | 
			
		||||
        Builder<T> permission(@NonNull String permission, @NonNull TriState defaultValue);
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Sets the aliases.
 | 
			
		||||
         *
 | 
			
		||||
         * @param aliases the aliases
 | 
			
		||||
         * @return the builder
 | 
			
		||||
         * @return this builder
 | 
			
		||||
         */
 | 
			
		||||
        Builder<T> aliases(@NonNull List<String> aliases);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -168,46 +191,62 @@ public interface Command {
 | 
			
		|||
         * Sets if this command is designed to be used only by server operators.
 | 
			
		||||
         *
 | 
			
		||||
         * @param suggestedOpOnly if this command is designed to be used only by server operators
 | 
			
		||||
         * @return the builder
 | 
			
		||||
         * @return this builder
 | 
			
		||||
         * @deprecated this method is not guaranteed to produce meaningful or expected results
 | 
			
		||||
         */
 | 
			
		||||
        @Deprecated(forRemoval = true)
 | 
			
		||||
        Builder<T> suggestedOpOnly(boolean suggestedOpOnly);
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Sets if this command is executable on console.
 | 
			
		||||
         *
 | 
			
		||||
         * @param executableOnConsole if this command is executable on console
 | 
			
		||||
         * @return the builder
 | 
			
		||||
         * @return this builder
 | 
			
		||||
         * @deprecated use {@link #isPlayerOnly()} instead (inverted)
 | 
			
		||||
         */
 | 
			
		||||
        @Deprecated(forRemoval = true)
 | 
			
		||||
        Builder<T> executableOnConsole(boolean executableOnConsole);
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Sets if this command can only be executed by players.
 | 
			
		||||
         *
 | 
			
		||||
         * @param playerOnly if this command is player only
 | 
			
		||||
         * @return this builder
 | 
			
		||||
         */
 | 
			
		||||
        Builder<T> playerOnly(boolean playerOnly);
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Sets if this command can only be executed by bedrock players.
 | 
			
		||||
         *
 | 
			
		||||
         * @param bedrockOnly if this command is bedrock only
 | 
			
		||||
         * @return this builder
 | 
			
		||||
         */
 | 
			
		||||
        Builder<T> bedrockOnly(boolean bedrockOnly);
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Sets the subcommands.
 | 
			
		||||
         *
 | 
			
		||||
         * @param subCommands the subcommands
 | 
			
		||||
         * @return the builder
 | 
			
		||||
         * @return this builder
 | 
			
		||||
         * @deprecated this method has no effect
 | 
			
		||||
         */
 | 
			
		||||
        Builder<T> subCommands(@NonNull List<String> subCommands);
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Sets if this command is bedrock only.
 | 
			
		||||
         *
 | 
			
		||||
         * @param bedrockOnly if this command is bedrock only
 | 
			
		||||
         * @return the builder
 | 
			
		||||
         */
 | 
			
		||||
        Builder<T> bedrockOnly(boolean bedrockOnly);
 | 
			
		||||
        @Deprecated(forRemoval = true)
 | 
			
		||||
        default Builder<T> subCommands(@NonNull List<String> subCommands) {
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Sets the {@link CommandExecutor} for this command.
 | 
			
		||||
         *
 | 
			
		||||
         * @param executor the command executor
 | 
			
		||||
         * @return the builder
 | 
			
		||||
         * @return this builder
 | 
			
		||||
         */
 | 
			
		||||
        Builder<T> executor(@NonNull CommandExecutor<T> executor);
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Builds the command.
 | 
			
		||||
         *
 | 
			
		||||
         * @return the command
 | 
			
		||||
         * @return a new command from this builder
 | 
			
		||||
         */
 | 
			
		||||
        @NonNull
 | 
			
		||||
        Command build();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,6 +26,10 @@
 | 
			
		|||
package org.geysermc.geyser.api.command;
 | 
			
		||||
 | 
			
		||||
import org.checkerframework.checker.nullness.qual.NonNull;
 | 
			
		||||
import org.checkerframework.checker.nullness.qual.Nullable;
 | 
			
		||||
import org.geysermc.geyser.api.connection.GeyserConnection;
 | 
			
		||||
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents an instance capable of sending commands.
 | 
			
		||||
| 
						 | 
				
			
			@ -64,6 +68,17 @@ public interface CommandSource {
 | 
			
		|||
     */
 | 
			
		||||
    boolean isConsole();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return a Java UUID if this source represents a player, otherwise null
 | 
			
		||||
     */
 | 
			
		||||
    @Nullable UUID playerUuid();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return a GeyserConnection if this source represents a Bedrock player that is connected
 | 
			
		||||
     * to this Geyser instance, otherwise null
 | 
			
		||||
     */
 | 
			
		||||
    @Nullable GeyserConnection connection();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the locale of the command source.
 | 
			
		||||
     *
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -132,4 +132,9 @@ public interface GeyserConnection extends Connection, CommandSource {
 | 
			
		|||
    @Deprecated
 | 
			
		||||
    @NonNull
 | 
			
		||||
    Set<String> fogEffects();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the current ping of the connection.
 | 
			
		||||
     */
 | 
			
		||||
    int ping();
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -81,4 +81,10 @@ public interface EntityData {
 | 
			
		|||
     * @return whether the movement is locked
 | 
			
		||||
     */
 | 
			
		||||
    boolean isMovementLocked();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sends a request to the Java server to switch the items in the main and offhand.
 | 
			
		||||
     * There is no guarantee of the server accepting the request.
 | 
			
		||||
     */
 | 
			
		||||
    void switchHands();
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -50,7 +50,7 @@ public interface GeyserDefineCommandsEvent extends Event {
 | 
			
		|||
    /**
 | 
			
		||||
     * Gets all the registered built-in {@link Command}s.
 | 
			
		||||
     *
 | 
			
		||||
     * @return all the registered built-in commands
 | 
			
		||||
     * @return all the registered built-in commands as an unmodifiable map
 | 
			
		||||
     */
 | 
			
		||||
    @NonNull
 | 
			
		||||
    Map<String, Command> commands();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,42 @@
 | 
			
		|||
/*
 | 
			
		||||
 * 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.api.event.lifecycle;
 | 
			
		||||
 | 
			
		||||
import org.geysermc.event.Event;
 | 
			
		||||
import org.geysermc.event.PostOrder;
 | 
			
		||||
import org.geysermc.geyser.api.permission.PermissionChecker;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Fired by any permission manager implementations that wish to add support for custom permission checking.
 | 
			
		||||
 * This event is not guaranteed to be fired - it is currently only fired on Geyser-Standalone and ViaProxy.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Subscribing to this event with an earlier {@link PostOrder} and registering a {@link PermissionChecker}
 | 
			
		||||
 * will result in that checker having a higher priority than others.
 | 
			
		||||
 */
 | 
			
		||||
public interface GeyserRegisterPermissionCheckersEvent extends Event {
 | 
			
		||||
 | 
			
		||||
    void register(PermissionChecker checker);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,51 @@
 | 
			
		|||
/*
 | 
			
		||||
 * 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.api.event.lifecycle;
 | 
			
		||||
 | 
			
		||||
import org.checkerframework.checker.nullness.qual.NonNull;
 | 
			
		||||
import org.geysermc.event.Event;
 | 
			
		||||
import org.geysermc.geyser.api.util.TriState;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Fired by anything that wishes to gather permission nodes and defaults.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * This event is not guaranteed to be fired, as certain Geyser platforms do not have a native permission system.
 | 
			
		||||
 * It can be expected to fire on Geyser-Spigot, Geyser-NeoForge, Geyser-Standalone, and Geyser-ViaProxy
 | 
			
		||||
 * It may be fired by a 3rd party regardless of the platform.
 | 
			
		||||
 */
 | 
			
		||||
public interface GeyserRegisterPermissionsEvent extends Event {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Registers a permission node and its default value with the firer.<p>
 | 
			
		||||
     * {@link TriState#TRUE} corresponds to all players having the permission by default.<br>
 | 
			
		||||
     * {@link TriState#NOT_SET} corresponds to only server operators having the permission by default (if such a concept exists on the platform).<br>
 | 
			
		||||
     * {@link TriState#FALSE} corresponds to no players having the permission by default.<br>
 | 
			
		||||
     *
 | 
			
		||||
     * @param permission the permission node to register
 | 
			
		||||
     * @param defaultValue the default value of the node
 | 
			
		||||
     */
 | 
			
		||||
    void register(@NonNull String permission, @NonNull TriState defaultValue);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -107,6 +107,15 @@ public interface Extension extends EventRegistrar {
 | 
			
		|||
        return this.extensionLoader().description(this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return the root command that all of this extension's commands will stem from.
 | 
			
		||||
     *         By default, this is the extension's id.
 | 
			
		||||
     */
 | 
			
		||||
    @NonNull
 | 
			
		||||
    default String rootCommand() {
 | 
			
		||||
        return this.description().id();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the extension's logger
 | 
			
		||||
     *
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -59,33 +59,46 @@ public interface ExtensionDescription {
 | 
			
		|||
    String main();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the extension's major api version
 | 
			
		||||
     * Represents the human api version that the extension requires.
 | 
			
		||||
     * See the <a href="https://github.com/geysermc/api/blob/master/geyser-versioning.md">Geyser version outline</a>)
 | 
			
		||||
     * for more details on the Geyser API version.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the extension's major api version
 | 
			
		||||
     * @return the extension's requested human api version
 | 
			
		||||
     */
 | 
			
		||||
    int humanApiVersion();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Represents the major api version that the extension requires.
 | 
			
		||||
     * See the <a href="https://github.com/geysermc/api/blob/master/geyser-versioning.md">Geyser version outline</a>)
 | 
			
		||||
     * for more details on the Geyser API version.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the extension's requested major api version
 | 
			
		||||
     */
 | 
			
		||||
    int majorApiVersion();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the extension's minor api version
 | 
			
		||||
     * Represents the minor api version that the extension requires.
 | 
			
		||||
     * See the <a href="https://github.com/geysermc/api/blob/master/geyser-versioning.md">Geyser version outline</a>)
 | 
			
		||||
     * for more details on the Geyser API version.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the extension's minor api version
 | 
			
		||||
     * @return the extension's requested minor api version
 | 
			
		||||
     */
 | 
			
		||||
    int minorApiVersion();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the extension's patch api version
 | 
			
		||||
     *
 | 
			
		||||
     * @return the extension's patch api version
 | 
			
		||||
     * No longer in use. Geyser is now using an adaption of the romantic versioning scheme.
 | 
			
		||||
     * See <a href="https://github.com/geysermc/api/blob/master/geyser-versioning.md">here</a> for details.
 | 
			
		||||
     */
 | 
			
		||||
    int patchApiVersion();
 | 
			
		||||
    @Deprecated(forRemoval = true)
 | 
			
		||||
    default int patchApiVersion() {
 | 
			
		||||
        return minorApiVersion();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the extension's api version.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the extension's api version
 | 
			
		||||
     * Returns the extension's requested Geyser Api version.
 | 
			
		||||
     */
 | 
			
		||||
    default String apiVersion() {
 | 
			
		||||
        return majorApiVersion() + "." + minorApiVersion() + "." + patchApiVersion();
 | 
			
		||||
        return humanApiVersion() + "." + majorApiVersion() + "." + minorApiVersion();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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.api.permission;
 | 
			
		||||
 | 
			
		||||
import org.checkerframework.checker.nullness.qual.NonNull;
 | 
			
		||||
import org.geysermc.geyser.api.command.CommandSource;
 | 
			
		||||
import org.geysermc.geyser.api.util.TriState;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Something capable of checking if a {@link CommandSource} has a permission
 | 
			
		||||
 */
 | 
			
		||||
@FunctionalInterface
 | 
			
		||||
public interface PermissionChecker {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the given source has a permission
 | 
			
		||||
     *
 | 
			
		||||
     * @param source the {@link CommandSource} whose permissions should be queried
 | 
			
		||||
     * @param permission the permission node to check
 | 
			
		||||
     * @return a {@link TriState} as the value of the node. {@link TriState#NOT_SET} generally means that the permission
 | 
			
		||||
     *         node itself was not found, and the source does not have such permission.
 | 
			
		||||
     *         {@link TriState#TRUE} and {@link TriState#FALSE} represent explicitly set values.
 | 
			
		||||
     */
 | 
			
		||||
    @NonNull
 | 
			
		||||
    TriState hasPermission(@NonNull CommandSource source, @NonNull String permission);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,5 +1,7 @@
 | 
			
		|||
dependencies {
 | 
			
		||||
    api(projects.core)
 | 
			
		||||
 | 
			
		||||
    implementation(libs.cloud.bungee)
 | 
			
		||||
    implementation(libs.adventure.text.serializer.bungeecord)
 | 
			
		||||
    compileOnlyApi(libs.bungeecord.proxy)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -8,13 +10,15 @@ platformRelocate("net.md_5.bungee.jni")
 | 
			
		|||
platformRelocate("com.fasterxml.jackson")
 | 
			
		||||
platformRelocate("io.netty.channel.kqueue") // This is not used because relocating breaks natives, but we must include it or else we get ClassDefNotFound
 | 
			
		||||
platformRelocate("net.kyori")
 | 
			
		||||
platformRelocate("org.incendo")
 | 
			
		||||
platformRelocate("io.leangen.geantyref") // provided by cloud, should also be relocated
 | 
			
		||||
platformRelocate("org.yaml") // Broken as of 1.20
 | 
			
		||||
 | 
			
		||||
// These dependencies are already present on the platform
 | 
			
		||||
provided(libs.bungeecord.proxy)
 | 
			
		||||
 | 
			
		||||
application {
 | 
			
		||||
    mainClass.set("org.geysermc.geyser.platform.bungeecord.GeyserBungeeMain")
 | 
			
		||||
tasks.withType<Jar> {
 | 
			
		||||
    manifest.attributes["Main-Class"] = "org.geysermc.geyser.platform.bungeecord.GeyserBungeeMain"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -27,6 +27,7 @@ package org.geysermc.geyser.platform.bungeecord;
 | 
			
		|||
 | 
			
		||||
import io.netty.channel.Channel;
 | 
			
		||||
import net.md_5.bungee.BungeeCord;
 | 
			
		||||
import net.md_5.bungee.api.CommandSender;
 | 
			
		||||
import net.md_5.bungee.api.config.ListenerInfo;
 | 
			
		||||
import net.md_5.bungee.api.plugin.Plugin;
 | 
			
		||||
import net.md_5.bungee.protocol.ProtocolConstants;
 | 
			
		||||
| 
						 | 
				
			
			@ -34,17 +35,20 @@ import org.checkerframework.checker.nullness.qual.NonNull;
 | 
			
		|||
import org.checkerframework.checker.nullness.qual.Nullable;
 | 
			
		||||
import org.geysermc.geyser.GeyserBootstrap;
 | 
			
		||||
import org.geysermc.geyser.GeyserImpl;
 | 
			
		||||
import org.geysermc.geyser.api.command.Command;
 | 
			
		||||
import org.geysermc.geyser.api.extension.Extension;
 | 
			
		||||
import org.geysermc.geyser.api.util.PlatformType;
 | 
			
		||||
import org.geysermc.geyser.command.GeyserCommandManager;
 | 
			
		||||
import org.geysermc.geyser.command.CommandRegistry;
 | 
			
		||||
import org.geysermc.geyser.command.CommandSourceConverter;
 | 
			
		||||
import org.geysermc.geyser.command.GeyserCommandSource;
 | 
			
		||||
import org.geysermc.geyser.configuration.GeyserConfiguration;
 | 
			
		||||
import org.geysermc.geyser.dump.BootstrapDumpInfo;
 | 
			
		||||
import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough;
 | 
			
		||||
import org.geysermc.geyser.ping.IGeyserPingPassthrough;
 | 
			
		||||
import org.geysermc.geyser.platform.bungeecord.command.GeyserBungeeCommandExecutor;
 | 
			
		||||
import org.geysermc.geyser.platform.bungeecord.command.BungeeCommandSource;
 | 
			
		||||
import org.geysermc.geyser.text.GeyserLocale;
 | 
			
		||||
import org.geysermc.geyser.util.FileUtils;
 | 
			
		||||
import org.incendo.cloud.CommandManager;
 | 
			
		||||
import org.incendo.cloud.bungee.BungeeCommandManager;
 | 
			
		||||
import org.incendo.cloud.execution.ExecutionCoordinator;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
| 
						 | 
				
			
			@ -54,21 +58,22 @@ import java.net.SocketAddress;
 | 
			
		|||
import java.nio.file.Path;
 | 
			
		||||
import java.nio.file.Paths;
 | 
			
		||||
import java.util.Collection;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.Optional;
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
import java.util.concurrent.TimeUnit;
 | 
			
		||||
 | 
			
		||||
public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
 | 
			
		||||
 | 
			
		||||
    private GeyserCommandManager geyserCommandManager;
 | 
			
		||||
    private CommandRegistry commandRegistry;
 | 
			
		||||
    private GeyserBungeeConfiguration geyserConfig;
 | 
			
		||||
    private GeyserBungeeInjector geyserInjector;
 | 
			
		||||
    private final GeyserBungeeLogger geyserLogger = new GeyserBungeeLogger(getLogger());
 | 
			
		||||
    private IGeyserPingPassthrough geyserBungeePingPassthrough;
 | 
			
		||||
 | 
			
		||||
    private GeyserImpl geyser;
 | 
			
		||||
 | 
			
		||||
    // We can't disable the plugin; hence we need to keep track of it manually
 | 
			
		||||
    private boolean disabled;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onLoad() {
 | 
			
		||||
        onGeyserInitialize();
 | 
			
		||||
| 
						 | 
				
			
			@ -93,16 +98,23 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
 | 
			
		|||
        }
 | 
			
		||||
 | 
			
		||||
        if (!this.loadConfig()) {
 | 
			
		||||
            disabled = true;
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        this.geyserLogger.setDebug(geyserConfig.isDebugMode());
 | 
			
		||||
        GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
 | 
			
		||||
        this.geyser = GeyserImpl.load(PlatformType.BUNGEECORD, this);
 | 
			
		||||
        this.geyserInjector = new GeyserBungeeInjector(this);
 | 
			
		||||
 | 
			
		||||
        // Registration of listeners occurs only once
 | 
			
		||||
        this.getProxy().getPluginManager().registerListener(this, new GeyserBungeeUpdateListener());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onEnable() {
 | 
			
		||||
        if (disabled) {
 | 
			
		||||
            return; // Config did not load properly!
 | 
			
		||||
        }
 | 
			
		||||
        // Big hack - Bungee does not provide us an event to listen to, so schedule a repeating
 | 
			
		||||
        // task that waits for a field to be filled which is set after the plugin enable
 | 
			
		||||
        // process is complete
 | 
			
		||||
| 
						 | 
				
			
			@ -143,10 +155,18 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
 | 
			
		|||
            this.geyserLogger.setDebug(geyserConfig.isDebugMode());
 | 
			
		||||
            GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
 | 
			
		||||
        } else {
 | 
			
		||||
            // For consistency with other platforms - create command manager before GeyserImpl#start()
 | 
			
		||||
            // This ensures the command events are called before the item/block ones are
 | 
			
		||||
            this.geyserCommandManager = new GeyserCommandManager(geyser);
 | 
			
		||||
            this.geyserCommandManager.init();
 | 
			
		||||
            var sourceConverter = new CommandSourceConverter<>(
 | 
			
		||||
                    CommandSender.class,
 | 
			
		||||
                    id -> getProxy().getPlayer(id),
 | 
			
		||||
                    () -> getProxy().getConsole(),
 | 
			
		||||
                    BungeeCommandSource::new
 | 
			
		||||
            );
 | 
			
		||||
            CommandManager<GeyserCommandSource> cloud = new BungeeCommandManager<>(
 | 
			
		||||
                    this,
 | 
			
		||||
                    ExecutionCoordinator.simpleCoordinator(),
 | 
			
		||||
                    sourceConverter
 | 
			
		||||
            );
 | 
			
		||||
            this.commandRegistry = new CommandRegistry(geyser, cloud, false); // applying root permission would be a breaking change because we can't register permission defaults
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Force-disable query if enabled, or else Geyser won't enable
 | 
			
		||||
| 
						 | 
				
			
			@ -181,16 +201,6 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
 | 
			
		|||
        }
 | 
			
		||||
 | 
			
		||||
        this.geyserInjector.initializeLocalChannel(this);
 | 
			
		||||
 | 
			
		||||
        this.getProxy().getPluginManager().registerCommand(this, new GeyserBungeeCommandExecutor("geyser", this.geyser, this.geyserCommandManager.getCommands()));
 | 
			
		||||
        for (Map.Entry<Extension, Map<String, Command>> entry : this.geyserCommandManager.extensionCommands().entrySet()) {
 | 
			
		||||
            Map<String, Command> commands = entry.getValue();
 | 
			
		||||
            if (commands.isEmpty()) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.getProxy().getPluginManager().registerCommand(this, new GeyserBungeeCommandExecutor(entry.getKey().description().id(), this.geyser, commands));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
| 
						 | 
				
			
			@ -226,8 +236,8 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public GeyserCommandManager getGeyserCommandManager() {
 | 
			
		||||
        return this.geyserCommandManager;
 | 
			
		||||
    public CommandRegistry getCommandRegistry() {
 | 
			
		||||
        return this.commandRegistry;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -29,8 +29,8 @@ import net.md_5.bungee.api.connection.ProxiedPlayer;
 | 
			
		|||
import net.md_5.bungee.api.event.PostLoginEvent;
 | 
			
		||||
import net.md_5.bungee.api.plugin.Listener;
 | 
			
		||||
import net.md_5.bungee.event.EventHandler;
 | 
			
		||||
import org.geysermc.geyser.Constants;
 | 
			
		||||
import org.geysermc.geyser.GeyserImpl;
 | 
			
		||||
import org.geysermc.geyser.Permissions;
 | 
			
		||||
import org.geysermc.geyser.platform.bungeecord.command.BungeeCommandSource;
 | 
			
		||||
import org.geysermc.geyser.util.VersionCheckUtils;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -40,7 +40,7 @@ public final class GeyserBungeeUpdateListener implements Listener {
 | 
			
		|||
    public void onPlayerJoin(final PostLoginEvent event) {
 | 
			
		||||
        if (GeyserImpl.getInstance().getConfig().isNotifyOnNewBedrockUpdate()) {
 | 
			
		||||
            final ProxiedPlayer player = event.getPlayer();
 | 
			
		||||
            if (player.hasPermission(Constants.UPDATE_PERMISSION)) {
 | 
			
		||||
            if (player.hasPermission(Permissions.CHECK_UPDATE)) {
 | 
			
		||||
                VersionCheckUtils.checkForGeyserUpdate(() -> new BungeeCommandSource(player));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -27,19 +27,22 @@ package org.geysermc.geyser.platform.bungeecord.command;
 | 
			
		|||
 | 
			
		||||
import net.kyori.adventure.text.Component;
 | 
			
		||||
import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer;
 | 
			
		||||
import net.md_5.bungee.api.CommandSender;
 | 
			
		||||
import net.md_5.bungee.api.chat.TextComponent;
 | 
			
		||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
 | 
			
		||||
import org.checkerframework.checker.nullness.qual.NonNull;
 | 
			
		||||
import org.checkerframework.checker.nullness.qual.Nullable;
 | 
			
		||||
import org.geysermc.geyser.command.GeyserCommandSource;
 | 
			
		||||
import org.geysermc.geyser.text.GeyserLocale;
 | 
			
		||||
 | 
			
		||||
import java.util.Locale;
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
 | 
			
		||||
public class BungeeCommandSource implements GeyserCommandSource {
 | 
			
		||||
 | 
			
		||||
    private final net.md_5.bungee.api.CommandSender handle;
 | 
			
		||||
    private final CommandSender handle;
 | 
			
		||||
 | 
			
		||||
    public BungeeCommandSource(net.md_5.bungee.api.CommandSender handle) {
 | 
			
		||||
    public BungeeCommandSource(CommandSender handle) {
 | 
			
		||||
        this.handle = handle;
 | 
			
		||||
        // Ensure even Java players' languages are loaded
 | 
			
		||||
        GeyserLocale.loadGeyserLocale(this.locale());
 | 
			
		||||
| 
						 | 
				
			
			@ -72,12 +75,20 @@ public class BungeeCommandSource implements GeyserCommandSource {
 | 
			
		|||
        return !(handle instanceof ProxiedPlayer);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public @Nullable UUID playerUuid() {
 | 
			
		||||
        if (handle instanceof ProxiedPlayer player) {
 | 
			
		||||
            return player.getUniqueId();
 | 
			
		||||
        }
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public String locale() {
 | 
			
		||||
        if (handle instanceof ProxiedPlayer player) {
 | 
			
		||||
            Locale locale = player.getLocale();
 | 
			
		||||
            if (locale != null) {
 | 
			
		||||
                // Locale can be null early on in the conneciton
 | 
			
		||||
                // Locale can be null early on in the connection
 | 
			
		||||
                return GeyserLocale.formatLocale(locale.getLanguage() + "_" + locale.getCountry());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -86,6 +97,12 @@ public class BungeeCommandSource implements GeyserCommandSource {
 | 
			
		|||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean hasPermission(String permission) {
 | 
			
		||||
        return handle.hasPermission(permission);
 | 
			
		||||
        // Handle blank permissions ourselves, as bungeecord only handles empty ones
 | 
			
		||||
        return permission.isBlank() || handle.hasPermission(permission);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Object handle() {
 | 
			
		||||
        return handle;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,89 +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.platform.bungeecord.command;
 | 
			
		||||
 | 
			
		||||
import net.md_5.bungee.api.ChatColor;
 | 
			
		||||
import net.md_5.bungee.api.CommandSender;
 | 
			
		||||
import net.md_5.bungee.api.plugin.Command;
 | 
			
		||||
import net.md_5.bungee.api.plugin.TabExecutor;
 | 
			
		||||
import org.geysermc.geyser.GeyserImpl;
 | 
			
		||||
import org.geysermc.geyser.command.GeyserCommand;
 | 
			
		||||
import org.geysermc.geyser.command.GeyserCommandExecutor;
 | 
			
		||||
import org.geysermc.geyser.session.GeyserSession;
 | 
			
		||||
import org.geysermc.geyser.text.GeyserLocale;
 | 
			
		||||
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
public class GeyserBungeeCommandExecutor extends Command implements TabExecutor {
 | 
			
		||||
    private final GeyserCommandExecutor commandExecutor;
 | 
			
		||||
 | 
			
		||||
    public GeyserBungeeCommandExecutor(String name, GeyserImpl geyser, Map<String, org.geysermc.geyser.api.command.Command> commands) {
 | 
			
		||||
        super(name);
 | 
			
		||||
 | 
			
		||||
        this.commandExecutor = new GeyserCommandExecutor(geyser, commands);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void execute(CommandSender sender, String[] args) {
 | 
			
		||||
        BungeeCommandSource commandSender = new BungeeCommandSource(sender);
 | 
			
		||||
        GeyserSession session = this.commandExecutor.getGeyserSession(commandSender);
 | 
			
		||||
 | 
			
		||||
        if (args.length > 0) {
 | 
			
		||||
            GeyserCommand command = this.commandExecutor.getCommand(args[0]);
 | 
			
		||||
            if (command != null) {
 | 
			
		||||
                if (!sender.hasPermission(command.permission())) {
 | 
			
		||||
                    String message = GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", commandSender.locale());
 | 
			
		||||
 | 
			
		||||
                    commandSender.sendMessage(ChatColor.RED + message);
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                if (command.isBedrockOnly() && session == null) {
 | 
			
		||||
                    String message = GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", commandSender.locale());
 | 
			
		||||
 | 
			
		||||
                    commandSender.sendMessage(ChatColor.RED + message);
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                command.execute(session, commandSender, args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]);
 | 
			
		||||
            } else {
 | 
			
		||||
                String message = GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.not_found", commandSender.locale());
 | 
			
		||||
                commandSender.sendMessage(ChatColor.RED + message);
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            this.commandExecutor.getCommand("help").execute(session, commandSender, new String[0]);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Iterable<String> onTabComplete(CommandSender sender, String[] args) {
 | 
			
		||||
        if (args.length == 1) {
 | 
			
		||||
            return commandExecutor.tabComplete(new BungeeCommandSource(sender));
 | 
			
		||||
        } else {
 | 
			
		||||
            return Collections.emptyList();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,7 +1,3 @@
 | 
			
		|||
plugins {
 | 
			
		||||
    application
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
architectury {
 | 
			
		||||
    platformSetupLoomIde()
 | 
			
		||||
    fabric()
 | 
			
		||||
| 
						 | 
				
			
			@ -25,10 +21,7 @@ dependencies {
 | 
			
		|||
    shadow(libs.protocol.connection) { isTransitive = false }
 | 
			
		||||
    shadow(libs.protocol.common) { isTransitive = false }
 | 
			
		||||
    shadow(libs.protocol.codec) { isTransitive = false }
 | 
			
		||||
    shadow(libs.minecraftauth) { isTransitive = false }
 | 
			
		||||
    shadow(libs.raknet) { isTransitive = false }
 | 
			
		||||
 | 
			
		||||
    // Consequences of shading + relocating mcauthlib: shadow/relocate mcpl!
 | 
			
		||||
    shadow(libs.mcprotocollib) { isTransitive = false }
 | 
			
		||||
 | 
			
		||||
    // Since we also relocate cloudburst protocol: shade erosion common
 | 
			
		||||
| 
						 | 
				
			
			@ -38,13 +31,12 @@ dependencies {
 | 
			
		|||
    shadow(projects.api) { isTransitive = false }
 | 
			
		||||
    shadow(projects.common) { isTransitive = false }
 | 
			
		||||
 | 
			
		||||
    // Permissions
 | 
			
		||||
    modImplementation(libs.fabric.permissions)
 | 
			
		||||
    include(libs.fabric.permissions)
 | 
			
		||||
    modImplementation(libs.cloud.fabric)
 | 
			
		||||
    include(libs.cloud.fabric)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
application {
 | 
			
		||||
    mainClass.set("org.geysermc.geyser.platform.fabric.GeyserFabricMain")
 | 
			
		||||
tasks.withType<Jar> {
 | 
			
		||||
    manifest.attributes["Main-Class"] = "org.geysermc.geyser.platform.fabric.GeyserFabricMain"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
relocate("org.cloudburstmc.netty")
 | 
			
		||||
| 
						 | 
				
			
			@ -67,4 +59,4 @@ modrinth {
 | 
			
		|||
    dependencies {
 | 
			
		||||
        required.project("fabric-api")
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,7 +25,6 @@
 | 
			
		|||
 | 
			
		||||
package org.geysermc.geyser.platform.fabric;
 | 
			
		||||
 | 
			
		||||
import me.lucko.fabric.api.permissions.v0.Permissions;
 | 
			
		||||
import net.fabricmc.api.EnvType;
 | 
			
		||||
import net.fabricmc.api.ModInitializer;
 | 
			
		||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
 | 
			
		||||
| 
						 | 
				
			
			@ -34,9 +33,16 @@ import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
 | 
			
		|||
import net.fabricmc.loader.api.FabricLoader;
 | 
			
		||||
import net.minecraft.commands.CommandSourceStack;
 | 
			
		||||
import net.minecraft.world.entity.player.Player;
 | 
			
		||||
import org.checkerframework.checker.nullness.qual.NonNull;
 | 
			
		||||
import org.geysermc.geyser.GeyserImpl;
 | 
			
		||||
import org.geysermc.geyser.command.CommandRegistry;
 | 
			
		||||
import org.geysermc.geyser.command.CommandSourceConverter;
 | 
			
		||||
import org.geysermc.geyser.command.GeyserCommandSource;
 | 
			
		||||
import org.geysermc.geyser.platform.mod.GeyserModBootstrap;
 | 
			
		||||
import org.geysermc.geyser.platform.mod.GeyserModUpdateListener;
 | 
			
		||||
import org.geysermc.geyser.platform.mod.command.ModCommandSource;
 | 
			
		||||
import org.incendo.cloud.CommandManager;
 | 
			
		||||
import org.incendo.cloud.execution.ExecutionCoordinator;
 | 
			
		||||
import org.incendo.cloud.fabric.FabricServerCommandManager;
 | 
			
		||||
 | 
			
		||||
public class GeyserFabricBootstrap extends GeyserModBootstrap implements ModInitializer {
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -70,20 +76,23 @@ public class GeyserFabricBootstrap extends GeyserModBootstrap implements ModInit
 | 
			
		|||
        ServerPlayConnectionEvents.JOIN.register((handler, $, $$) -> GeyserModUpdateListener.onPlayReady(handler.getPlayer()));
 | 
			
		||||
 | 
			
		||||
        this.onGeyserInitialize();
 | 
			
		||||
 | 
			
		||||
        var sourceConverter = CommandSourceConverter.layered(
 | 
			
		||||
                CommandSourceStack.class,
 | 
			
		||||
                id -> getServer().getPlayerList().getPlayer(id),
 | 
			
		||||
                Player::createCommandSourceStack,
 | 
			
		||||
                () -> getServer().createCommandSourceStack(), // NPE if method reference is used, since server is not available yet
 | 
			
		||||
                ModCommandSource::new
 | 
			
		||||
        );
 | 
			
		||||
        CommandManager<GeyserCommandSource> cloud = new FabricServerCommandManager<>(
 | 
			
		||||
                ExecutionCoordinator.simpleCoordinator(),
 | 
			
		||||
                sourceConverter
 | 
			
		||||
        );
 | 
			
		||||
        this.setCommandRegistry(new CommandRegistry(GeyserImpl.getInstance(), cloud, false)); // applying root permission would be a breaking change because we can't register permission defaults
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean isServer() {
 | 
			
		||||
        return FabricLoader.getInstance().getEnvironmentType().equals(EnvType.SERVER);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean hasPermission(@NonNull Player source, @NonNull String permissionNode) {
 | 
			
		||||
        return Permissions.check(source, permissionNode);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean hasPermission(@NonNull CommandSourceStack source, @NonNull String permissionNode, int permissionLevel) {
 | 
			
		||||
        return Permissions.check(source, permissionNode, permissionLevel);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,10 +1,7 @@
 | 
			
		|||
plugins {
 | 
			
		||||
    application
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// This is provided by "org.cloudburstmc.math.mutable" too, so yeet.
 | 
			
		||||
// NeoForge's class loader is *really* annoying.
 | 
			
		||||
provided("org.cloudburstmc.math", "api")
 | 
			
		||||
provided("com.google.errorprone", "error_prone_annotations")
 | 
			
		||||
 | 
			
		||||
architectury {
 | 
			
		||||
    platformSetupLoomIde()
 | 
			
		||||
| 
						 | 
				
			
			@ -37,10 +34,13 @@ dependencies {
 | 
			
		|||
 | 
			
		||||
    // Include all transitive deps of core via JiJ
 | 
			
		||||
    includeTransitive(projects.core)
 | 
			
		||||
 | 
			
		||||
    modImplementation(libs.cloud.neoforge)
 | 
			
		||||
    include(libs.cloud.neoforge)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
application {
 | 
			
		||||
    mainClass.set("org.geysermc.geyser.platform.forge.GeyserNeoForgeMain")
 | 
			
		||||
tasks.withType<Jar> {
 | 
			
		||||
    manifest.attributes["Main-Class"] = "org.geysermc.geyser.platform.neoforge.GeyserNeoForgeMain"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tasks {
 | 
			
		||||
| 
						 | 
				
			
			@ -56,4 +56,4 @@ tasks {
 | 
			
		|||
modrinth {
 | 
			
		||||
    loaders.add("neoforge")
 | 
			
		||||
    uploadFile.set(tasks.getByPath("remapModrinthJar"))
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -27,6 +27,7 @@ package org.geysermc.geyser.platform.neoforge;
 | 
			
		|||
 | 
			
		||||
import net.minecraft.commands.CommandSourceStack;
 | 
			
		||||
import net.minecraft.world.entity.player.Player;
 | 
			
		||||
import net.neoforged.bus.api.EventPriority;
 | 
			
		||||
import net.neoforged.fml.ModContainer;
 | 
			
		||||
import net.neoforged.fml.common.Mod;
 | 
			
		||||
import net.neoforged.fml.loading.FMLLoader;
 | 
			
		||||
| 
						 | 
				
			
			@ -35,15 +36,22 @@ import net.neoforged.neoforge.event.GameShuttingDownEvent;
 | 
			
		|||
import net.neoforged.neoforge.event.entity.player.PlayerEvent;
 | 
			
		||||
import net.neoforged.neoforge.event.server.ServerStartedEvent;
 | 
			
		||||
import net.neoforged.neoforge.event.server.ServerStoppingEvent;
 | 
			
		||||
import org.checkerframework.checker.nullness.qual.NonNull;
 | 
			
		||||
import net.neoforged.neoforge.server.permission.events.PermissionGatherEvent;
 | 
			
		||||
import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent;
 | 
			
		||||
import org.geysermc.geyser.command.CommandSourceConverter;
 | 
			
		||||
import org.geysermc.geyser.command.GeyserCommandSource;
 | 
			
		||||
import org.geysermc.geyser.platform.mod.GeyserModBootstrap;
 | 
			
		||||
import org.geysermc.geyser.platform.mod.GeyserModUpdateListener;
 | 
			
		||||
import org.geysermc.geyser.platform.mod.command.ModCommandSource;
 | 
			
		||||
import org.incendo.cloud.CommandManager;
 | 
			
		||||
import org.incendo.cloud.execution.ExecutionCoordinator;
 | 
			
		||||
import org.incendo.cloud.neoforge.NeoForgeServerCommandManager;
 | 
			
		||||
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
 | 
			
		||||
@Mod(ModConstants.MOD_ID)
 | 
			
		||||
public class GeyserNeoForgeBootstrap extends GeyserModBootstrap {
 | 
			
		||||
 | 
			
		||||
    private final GeyserNeoForgePermissionHandler permissionHandler = new GeyserNeoForgePermissionHandler();
 | 
			
		||||
 | 
			
		||||
    public GeyserNeoForgeBootstrap(ModContainer container) {
 | 
			
		||||
        super(new GeyserNeoForgePlatform(container));
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -56,9 +64,25 @@ public class GeyserNeoForgeBootstrap extends GeyserModBootstrap {
 | 
			
		|||
 | 
			
		||||
        NeoForge.EVENT_BUS.addListener(this::onServerStopping);
 | 
			
		||||
        NeoForge.EVENT_BUS.addListener(this::onPlayerJoin);
 | 
			
		||||
        NeoForge.EVENT_BUS.addListener(this.permissionHandler::onPermissionGather);
 | 
			
		||||
 | 
			
		||||
        NeoForge.EVENT_BUS.addListener(EventPriority.HIGHEST, this::onPermissionGather);
 | 
			
		||||
 | 
			
		||||
        this.onGeyserInitialize();
 | 
			
		||||
 | 
			
		||||
        var sourceConverter = CommandSourceConverter.layered(
 | 
			
		||||
                CommandSourceStack.class,
 | 
			
		||||
                id -> getServer().getPlayerList().getPlayer(id),
 | 
			
		||||
                Player::createCommandSourceStack,
 | 
			
		||||
                () -> getServer().createCommandSourceStack(),
 | 
			
		||||
                ModCommandSource::new
 | 
			
		||||
        );
 | 
			
		||||
        CommandManager<GeyserCommandSource> cloud = new NeoForgeServerCommandManager<>(
 | 
			
		||||
                ExecutionCoordinator.simpleCoordinator(),
 | 
			
		||||
                sourceConverter
 | 
			
		||||
        );
 | 
			
		||||
        GeyserNeoForgeCommandRegistry registry = new GeyserNeoForgeCommandRegistry(getGeyser(), cloud);
 | 
			
		||||
        this.setCommandRegistry(registry);
 | 
			
		||||
        NeoForge.EVENT_BUS.addListener(EventPriority.LOWEST, registry::onPermissionGatherForUndefined);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void onServerStarted(ServerStartedEvent event) {
 | 
			
		||||
| 
						 | 
				
			
			@ -87,13 +111,17 @@ public class GeyserNeoForgeBootstrap extends GeyserModBootstrap {
 | 
			
		|||
        return FMLLoader.getDist().isDedicatedServer();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean hasPermission(@NonNull Player source, @NonNull String permissionNode) {
 | 
			
		||||
        return this.permissionHandler.hasPermission(source, permissionNode);
 | 
			
		||||
    }
 | 
			
		||||
    private void onPermissionGather(PermissionGatherEvent.Nodes event) {
 | 
			
		||||
        getGeyser().eventBus().fire(
 | 
			
		||||
            (GeyserRegisterPermissionsEvent) (permission, defaultValue) -> {
 | 
			
		||||
                Objects.requireNonNull(permission, "permission");
 | 
			
		||||
                Objects.requireNonNull(defaultValue, "permission default for " + permission);
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean hasPermission(@NonNull CommandSourceStack source, @NonNull String permissionNode, int permissionLevel) {
 | 
			
		||||
        return this.permissionHandler.hasPermission(source, permissionNode, permissionLevel);
 | 
			
		||||
                if (permission.isBlank()) {
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                PermissionUtils.register(permission, defaultValue, event);
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,101 @@
 | 
			
		|||
/*
 | 
			
		||||
 * 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.platform.neoforge;
 | 
			
		||||
 | 
			
		||||
import net.neoforged.neoforge.server.permission.events.PermissionGatherEvent;
 | 
			
		||||
import org.geysermc.geyser.GeyserImpl;
 | 
			
		||||
import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent;
 | 
			
		||||
import org.geysermc.geyser.api.util.TriState;
 | 
			
		||||
import org.geysermc.geyser.command.CommandRegistry;
 | 
			
		||||
import org.geysermc.geyser.command.GeyserCommand;
 | 
			
		||||
import org.geysermc.geyser.command.GeyserCommandSource;
 | 
			
		||||
import org.incendo.cloud.CommandManager;
 | 
			
		||||
import org.incendo.cloud.neoforge.PermissionNotRegisteredException;
 | 
			
		||||
 | 
			
		||||
import java.util.HashSet;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
 | 
			
		||||
public class GeyserNeoForgeCommandRegistry extends CommandRegistry {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Permissions with an undefined permission default. Use Set to not register the same fallback more than once.
 | 
			
		||||
     * NeoForge requires that all permissions are registered, and cloud-neoforge follows that.
 | 
			
		||||
     * This is unlike most platforms, on which we wouldn't register a permission if no default was provided.
 | 
			
		||||
     */
 | 
			
		||||
    private final Set<String> undefinedPermissions = new HashSet<>();
 | 
			
		||||
 | 
			
		||||
    public GeyserNeoForgeCommandRegistry(GeyserImpl geyser, CommandManager<GeyserCommandSource> cloud) {
 | 
			
		||||
        super(geyser, cloud);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void register(GeyserCommand command, Map<String, GeyserCommand> commands) {
 | 
			
		||||
        super.register(command, commands);
 | 
			
		||||
 | 
			
		||||
        // FIRST STAGE: Collect all permissions that may have undefined defaults.
 | 
			
		||||
        if (!command.permission().isBlank() && command.permissionDefault() == null) {
 | 
			
		||||
            // Permission requirement exists but no default value specified.
 | 
			
		||||
            undefinedPermissions.add(command.permission());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onRegisterPermissions(GeyserRegisterPermissionsEvent event) {
 | 
			
		||||
        super.onRegisterPermissions(event);
 | 
			
		||||
 | 
			
		||||
        // SECOND STAGE
 | 
			
		||||
        // Now that we are aware of all commands, we can eliminate some incorrect assumptions.
 | 
			
		||||
        // Example: two commands may have the same permission, but only of them defines a permission default.
 | 
			
		||||
        undefinedPermissions.removeAll(permissionDefaults.keySet());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Registers permissions with possibly undefined defaults.
 | 
			
		||||
     * Should be subscribed late to allow extensions and mods to register a desired permission default first.
 | 
			
		||||
     */
 | 
			
		||||
    void onPermissionGatherForUndefined(PermissionGatherEvent.Nodes event) {
 | 
			
		||||
        // THIRD STAGE
 | 
			
		||||
        for (String permission : undefinedPermissions) {
 | 
			
		||||
            if (PermissionUtils.register(permission, TriState.NOT_SET, event)) {
 | 
			
		||||
                // The permission was not already registered
 | 
			
		||||
                geyser.getLogger().debug("Registered permission " + permission + " with fallback default value of NOT_SET");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean hasPermission(GeyserCommandSource source, String permission) {
 | 
			
		||||
        // NeoForgeServerCommandManager will throw this exception if the permission is not registered to the server.
 | 
			
		||||
        // We can't realistically ensure that every permission is registered (calls by API users), so we catch this.
 | 
			
		||||
        // This works for our calls, but not for cloud's internal usage. For that case, see above.
 | 
			
		||||
        try {
 | 
			
		||||
            return super.hasPermission(source, permission);
 | 
			
		||||
        } catch (PermissionNotRegisteredException e) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,149 +0,0 @@
 | 
			
		|||
/*
 | 
			
		||||
 * 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.platform.neoforge;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.commands.CommandSourceStack;
 | 
			
		||||
import net.minecraft.server.level.ServerPlayer;
 | 
			
		||||
import net.minecraft.world.entity.player.Player;
 | 
			
		||||
import net.neoforged.neoforge.server.permission.PermissionAPI;
 | 
			
		||||
import net.neoforged.neoforge.server.permission.events.PermissionGatherEvent;
 | 
			
		||||
import net.neoforged.neoforge.server.permission.nodes.PermissionDynamicContextKey;
 | 
			
		||||
import net.neoforged.neoforge.server.permission.nodes.PermissionNode;
 | 
			
		||||
import net.neoforged.neoforge.server.permission.nodes.PermissionType;
 | 
			
		||||
import net.neoforged.neoforge.server.permission.nodes.PermissionTypes;
 | 
			
		||||
import org.checkerframework.checker.nullness.qual.NonNull;
 | 
			
		||||
import org.geysermc.geyser.Constants;
 | 
			
		||||
import org.geysermc.geyser.GeyserImpl;
 | 
			
		||||
import org.geysermc.geyser.api.command.Command;
 | 
			
		||||
import org.geysermc.geyser.command.GeyserCommandManager;
 | 
			
		||||
 | 
			
		||||
import java.lang.reflect.Constructor;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
public class GeyserNeoForgePermissionHandler {
 | 
			
		||||
 | 
			
		||||
    private static final Constructor<?> PERMISSION_NODE_CONSTRUCTOR;
 | 
			
		||||
 | 
			
		||||
    static {
 | 
			
		||||
        try {
 | 
			
		||||
            @SuppressWarnings("rawtypes")
 | 
			
		||||
            Constructor<PermissionNode> constructor = PermissionNode.class.getDeclaredConstructor(
 | 
			
		||||
                    String.class,
 | 
			
		||||
                    PermissionType.class,
 | 
			
		||||
                    PermissionNode.PermissionResolver.class,
 | 
			
		||||
                    PermissionDynamicContextKey[].class
 | 
			
		||||
            );
 | 
			
		||||
            constructor.setAccessible(true);
 | 
			
		||||
            PERMISSION_NODE_CONSTRUCTOR = constructor;
 | 
			
		||||
        } catch (NoSuchMethodException e) {
 | 
			
		||||
            throw new RuntimeException("Unable to construct PermissionNode!", e);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private final Map<String, PermissionNode<Boolean>> permissionNodes = new HashMap<>();
 | 
			
		||||
 | 
			
		||||
    public void onPermissionGather(PermissionGatherEvent.Nodes event) {
 | 
			
		||||
        this.registerNode(Constants.UPDATE_PERMISSION, event);
 | 
			
		||||
 | 
			
		||||
        GeyserCommandManager commandManager = GeyserImpl.getInstance().commandManager();
 | 
			
		||||
        for (Map.Entry<String, Command> entry : commandManager.commands().entrySet()) {
 | 
			
		||||
            Command command = entry.getValue();
 | 
			
		||||
 | 
			
		||||
            // Don't register aliases
 | 
			
		||||
            if (!command.name().equals(entry.getKey())) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.registerNode(command.permission(), event);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (Map<String, Command> commands : commandManager.extensionCommands().values()) {
 | 
			
		||||
            for (Map.Entry<String, Command> entry : commands.entrySet()) {
 | 
			
		||||
                Command command = entry.getValue();
 | 
			
		||||
 | 
			
		||||
                // Don't register aliases
 | 
			
		||||
                if (!command.name().equals(entry.getKey())) {
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                this.registerNode(command.permission(), event);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public boolean hasPermission(@NonNull Player source, @NonNull String permissionNode) {
 | 
			
		||||
        PermissionNode<Boolean> node = this.permissionNodes.get(permissionNode);
 | 
			
		||||
        if (node == null) {
 | 
			
		||||
            GeyserImpl.getInstance().getLogger().warning("Unable to find permission node " + permissionNode);
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return PermissionAPI.getPermission((ServerPlayer) source, node);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public boolean hasPermission(@NonNull CommandSourceStack source, @NonNull String permissionNode, int permissionLevel) {
 | 
			
		||||
        if (!source.isPlayer()) {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        assert source.getPlayer() != null;
 | 
			
		||||
        boolean permission = this.hasPermission(source.getPlayer(), permissionNode);
 | 
			
		||||
        if (!permission) {
 | 
			
		||||
            return source.getPlayer().hasPermissions(permissionLevel);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void registerNode(String node, PermissionGatherEvent.Nodes event) {
 | 
			
		||||
        PermissionNode<Boolean> permissionNode = this.createNode(node);
 | 
			
		||||
 | 
			
		||||
        // NeoForge likes to crash if you try and register a duplicate node
 | 
			
		||||
        if (!event.getNodes().contains(permissionNode)) {
 | 
			
		||||
            event.addNodes(permissionNode);
 | 
			
		||||
            this.permissionNodes.put(node, permissionNode);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @SuppressWarnings("unchecked")
 | 
			
		||||
    private PermissionNode<Boolean> createNode(String node) {
 | 
			
		||||
        // The typical constructors in PermissionNode require a
 | 
			
		||||
        // mod id, which means our permission nodes end up becoming
 | 
			
		||||
        // geyser_neoforge.<node> instead of just <node>. We work around
 | 
			
		||||
        // this by using reflection to access the constructor that
 | 
			
		||||
        // doesn't require a mod id or ResourceLocation.
 | 
			
		||||
        try {
 | 
			
		||||
            return (PermissionNode<Boolean>) PERMISSION_NODE_CONSTRUCTOR.newInstance(
 | 
			
		||||
                    node,
 | 
			
		||||
                    PermissionTypes.BOOLEAN,
 | 
			
		||||
                    (PermissionNode.PermissionResolver<Boolean>) (player, playerUUID, context) -> false,
 | 
			
		||||
                    new PermissionDynamicContextKey[0]
 | 
			
		||||
            );
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            throw new RuntimeException("Unable to create permission node " + node, e);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,79 @@
 | 
			
		|||
/*
 | 
			
		||||
 * 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.platform.neoforge;
 | 
			
		||||
 | 
			
		||||
import net.neoforged.neoforge.server.permission.events.PermissionGatherEvent;
 | 
			
		||||
import net.neoforged.neoforge.server.permission.nodes.PermissionNode;
 | 
			
		||||
import net.neoforged.neoforge.server.permission.nodes.PermissionTypes;
 | 
			
		||||
import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent;
 | 
			
		||||
import org.geysermc.geyser.api.util.TriState;
 | 
			
		||||
import org.geysermc.geyser.platform.neoforge.mixin.PermissionNodeMixin;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Common logic for handling the more complicated way we have to register permission on NeoForge
 | 
			
		||||
 */
 | 
			
		||||
public class PermissionUtils {
 | 
			
		||||
 | 
			
		||||
    private PermissionUtils() {
 | 
			
		||||
        //no
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Registers the given permission and its default value to the event. If the permission has the same name as one
 | 
			
		||||
     * that has already been registered to the event, it will not be registered. In other words, it will not override.
 | 
			
		||||
     *
 | 
			
		||||
     * @param permission the permission to register
 | 
			
		||||
     * @param permissionDefault the permission's default value. See {@link GeyserRegisterPermissionsEvent#register(String, TriState)} for TriState meanings.
 | 
			
		||||
     * @param event the registration event
 | 
			
		||||
     * @return true if the permission was registered
 | 
			
		||||
     */
 | 
			
		||||
    public static boolean register(String permission, TriState permissionDefault, PermissionGatherEvent.Nodes event) {
 | 
			
		||||
        // NeoForge likes to crash if you try and register a duplicate node
 | 
			
		||||
        if (event.getNodes().stream().noneMatch(n -> n.getNodeName().equals(permission))) {
 | 
			
		||||
            PermissionNode<Boolean> node = createNode(permission, permissionDefault);
 | 
			
		||||
            event.addNodes(node);
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static PermissionNode<Boolean> createNode(String node, TriState permissionDefault) {
 | 
			
		||||
        return PermissionNodeMixin.geyser$construct(
 | 
			
		||||
            node,
 | 
			
		||||
            PermissionTypes.BOOLEAN,
 | 
			
		||||
            (player, playerUUID, context) -> switch (permissionDefault) {
 | 
			
		||||
                case TRUE -> true;
 | 
			
		||||
                case FALSE -> false;
 | 
			
		||||
                case NOT_SET -> {
 | 
			
		||||
                    if (player != null) {
 | 
			
		||||
                        yield player.createCommandSourceStack().hasPermission(player.server.getOperatorUserPermissionLevel());
 | 
			
		||||
                    }
 | 
			
		||||
                    yield false; // NeoForge javadocs say player is null in the case of an offline player.
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,48 @@
 | 
			
		|||
/*
 | 
			
		||||
 * 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.platform.neoforge.mixin;
 | 
			
		||||
 | 
			
		||||
import net.neoforged.neoforge.server.permission.nodes.PermissionDynamicContextKey;
 | 
			
		||||
import net.neoforged.neoforge.server.permission.nodes.PermissionNode;
 | 
			
		||||
import net.neoforged.neoforge.server.permission.nodes.PermissionType;
 | 
			
		||||
import org.spongepowered.asm.mixin.Mixin;
 | 
			
		||||
import org.spongepowered.asm.mixin.gen.Invoker;
 | 
			
		||||
 | 
			
		||||
@Mixin(value = PermissionNode.class, remap = false) // this is API - do not remap
 | 
			
		||||
public interface PermissionNodeMixin {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Invokes the matching private constructor in {@link PermissionNode}.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * The typical constructors in PermissionNode require a mod id, which means our permission nodes
 | 
			
		||||
     * would end up becoming {@code geyser_neoforge.<node>} instead of just {@code <node>}.
 | 
			
		||||
     */
 | 
			
		||||
    @SuppressWarnings("rawtypes") // the varargs
 | 
			
		||||
    @Invoker("<init>")
 | 
			
		||||
    static <T> PermissionNode<T> geyser$construct(String nodeName, PermissionType<T> type, PermissionNode.PermissionResolver<T> defaultResolver, PermissionDynamicContextKey... dynamics) {
 | 
			
		||||
        throw new IllegalStateException();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -11,6 +11,8 @@ authors="GeyserMC"
 | 
			
		|||
description="${description}"
 | 
			
		||||
[[mixins]]
 | 
			
		||||
config = "geyser.mixins.json"
 | 
			
		||||
[[mixins]]
 | 
			
		||||
config = "geyser_neoforge.mixins.json"
 | 
			
		||||
[[dependencies.geyser_neoforge]]
 | 
			
		||||
    modId="neoforge"
 | 
			
		||||
    type="required"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,12 @@
 | 
			
		|||
{
 | 
			
		||||
  "required": true,
 | 
			
		||||
  "minVersion": "0.8",
 | 
			
		||||
  "package": "org.geysermc.geyser.platform.neoforge.mixin",
 | 
			
		||||
  "compatibilityLevel": "JAVA_17",
 | 
			
		||||
  "mixins": [
 | 
			
		||||
    "PermissionNodeMixin"
 | 
			
		||||
  ],
 | 
			
		||||
  "injectors": {
 | 
			
		||||
    "defaultRequire": 1
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -25,30 +25,21 @@
 | 
			
		|||
 | 
			
		||||
package org.geysermc.geyser.platform.mod;
 | 
			
		||||
 | 
			
		||||
import com.mojang.brigadier.arguments.StringArgumentType;
 | 
			
		||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
 | 
			
		||||
import lombok.Getter;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import lombok.Setter;
 | 
			
		||||
import net.minecraft.commands.CommandSourceStack;
 | 
			
		||||
import net.minecraft.commands.Commands;
 | 
			
		||||
import net.minecraft.server.MinecraftServer;
 | 
			
		||||
import net.minecraft.world.entity.player.Player;
 | 
			
		||||
import org.checkerframework.checker.nullness.qual.NonNull;
 | 
			
		||||
import org.checkerframework.checker.nullness.qual.Nullable;
 | 
			
		||||
import org.geysermc.geyser.GeyserBootstrap;
 | 
			
		||||
import org.geysermc.geyser.GeyserImpl;
 | 
			
		||||
import org.geysermc.geyser.GeyserLogger;
 | 
			
		||||
import org.geysermc.geyser.api.command.Command;
 | 
			
		||||
import org.geysermc.geyser.api.extension.Extension;
 | 
			
		||||
import org.geysermc.geyser.command.GeyserCommand;
 | 
			
		||||
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.WorldManager;
 | 
			
		||||
import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough;
 | 
			
		||||
import org.geysermc.geyser.ping.IGeyserPingPassthrough;
 | 
			
		||||
import org.geysermc.geyser.platform.mod.command.GeyserModCommandExecutor;
 | 
			
		||||
import org.geysermc.geyser.platform.mod.platform.GeyserModPlatform;
 | 
			
		||||
import org.geysermc.geyser.platform.mod.world.GeyserModWorldManager;
 | 
			
		||||
import org.geysermc.geyser.text.GeyserLocale;
 | 
			
		||||
| 
						 | 
				
			
			@ -59,7 +50,6 @@ import java.io.IOException;
 | 
			
		|||
import java.io.InputStream;
 | 
			
		||||
import java.net.SocketAddress;
 | 
			
		||||
import java.nio.file.Path;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
 | 
			
		||||
@RequiredArgsConstructor
 | 
			
		||||
| 
						 | 
				
			
			@ -70,13 +60,15 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap {
 | 
			
		|||
 | 
			
		||||
    private final GeyserModPlatform platform;
 | 
			
		||||
 | 
			
		||||
    @Getter
 | 
			
		||||
    private GeyserImpl geyser;
 | 
			
		||||
    private Path dataFolder;
 | 
			
		||||
 | 
			
		||||
    @Setter
 | 
			
		||||
    @Setter @Getter
 | 
			
		||||
    private MinecraftServer server;
 | 
			
		||||
 | 
			
		||||
    private GeyserCommandManager geyserCommandManager;
 | 
			
		||||
    @Setter
 | 
			
		||||
    private CommandRegistry commandRegistry;
 | 
			
		||||
    private GeyserModConfiguration geyserConfig;
 | 
			
		||||
    private GeyserModInjector geyserInjector;
 | 
			
		||||
    private final GeyserModLogger geyserLogger = new GeyserModLogger();
 | 
			
		||||
| 
						 | 
				
			
			@ -94,10 +86,6 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap {
 | 
			
		|||
        this.geyserLogger.setDebug(geyserConfig.isDebugMode());
 | 
			
		||||
        GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
 | 
			
		||||
        this.geyser = GeyserImpl.load(this.platform.platformType(), this);
 | 
			
		||||
 | 
			
		||||
        // Create command manager here, since the permission handler on neo needs it
 | 
			
		||||
        this.geyserCommandManager = new GeyserCommandManager(geyser);
 | 
			
		||||
        this.geyserCommandManager.init();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void onGeyserEnable() {
 | 
			
		||||
| 
						 | 
				
			
			@ -130,50 +118,6 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap {
 | 
			
		|||
        if (isServer()) {
 | 
			
		||||
            this.geyserInjector.initializeLocalChannel(this);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Start command building
 | 
			
		||||
        // Set just "geyser" as the help command
 | 
			
		||||
        GeyserModCommandExecutor helpExecutor = new GeyserModCommandExecutor(geyser,
 | 
			
		||||
                (GeyserCommand) geyser.commandManager().getCommands().get("help"));
 | 
			
		||||
        LiteralArgumentBuilder<CommandSourceStack> builder = Commands.literal("geyser").executes(helpExecutor);
 | 
			
		||||
 | 
			
		||||
        // Register all subcommands as valid
 | 
			
		||||
        for (Map.Entry<String, Command> command : geyser.commandManager().getCommands().entrySet()) {
 | 
			
		||||
            GeyserModCommandExecutor executor = new GeyserModCommandExecutor(geyser, (GeyserCommand) command.getValue());
 | 
			
		||||
            builder.then(Commands.literal(command.getKey())
 | 
			
		||||
                    .executes(executor)
 | 
			
		||||
                    // Could also test for Bedrock but depending on when this is called it may backfire
 | 
			
		||||
                    .requires(executor::testPermission)
 | 
			
		||||
                    // Allows parsing of arguments; e.g. for /geyser dump logs or the connectiontest command
 | 
			
		||||
                    .then(Commands.argument("args", StringArgumentType.greedyString())
 | 
			
		||||
                            .executes(context -> executor.runWithArgs(context, StringArgumentType.getString(context, "args")))
 | 
			
		||||
                            .requires(executor::testPermission)));
 | 
			
		||||
        }
 | 
			
		||||
        server.getCommands().getDispatcher().register(builder);
 | 
			
		||||
 | 
			
		||||
        // Register extension commands
 | 
			
		||||
        for (Map.Entry<Extension, Map<String, Command>> extensionMapEntry : geyser.commandManager().extensionCommands().entrySet()) {
 | 
			
		||||
            Map<String, Command> extensionCommands = extensionMapEntry.getValue();
 | 
			
		||||
            if (extensionCommands.isEmpty()) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Register help command for just "/<extensionId>"
 | 
			
		||||
            GeyserModCommandExecutor extensionHelpExecutor = new GeyserModCommandExecutor(geyser,
 | 
			
		||||
                    (GeyserCommand) extensionCommands.get("help"));
 | 
			
		||||
            LiteralArgumentBuilder<CommandSourceStack> extCmdBuilder = Commands.literal(extensionMapEntry.getKey().description().id()).executes(extensionHelpExecutor);
 | 
			
		||||
 | 
			
		||||
            for (Map.Entry<String, Command> command : extensionCommands.entrySet()) {
 | 
			
		||||
                GeyserModCommandExecutor executor = new GeyserModCommandExecutor(geyser, (GeyserCommand) command.getValue());
 | 
			
		||||
                extCmdBuilder.then(Commands.literal(command.getKey())
 | 
			
		||||
                        .executes(executor)
 | 
			
		||||
                        .requires(executor::testPermission)
 | 
			
		||||
                        .then(Commands.argument("args", StringArgumentType.greedyString())
 | 
			
		||||
                                .executes(context -> executor.runWithArgs(context, StringArgumentType.getString(context, "args")))
 | 
			
		||||
                                .requires(executor::testPermission)));
 | 
			
		||||
            }
 | 
			
		||||
            server.getCommands().getDispatcher().register(extCmdBuilder);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
| 
						 | 
				
			
			@ -206,8 +150,8 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public GeyserCommandManager getGeyserCommandManager() {
 | 
			
		||||
        return geyserCommandManager;
 | 
			
		||||
    public CommandRegistry getCommandRegistry() {
 | 
			
		||||
        return commandRegistry;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
| 
						 | 
				
			
			@ -235,6 +179,7 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap {
 | 
			
		|||
        return this.server.getServerVersion();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @SuppressWarnings("ConstantConditions") // Certain IDEA installations think that ip cannot be null
 | 
			
		||||
    @NonNull
 | 
			
		||||
    @Override
 | 
			
		||||
    public String getServerBindAddress() {
 | 
			
		||||
| 
						 | 
				
			
			@ -270,10 +215,6 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap {
 | 
			
		|||
        return this.platform.resolveResource(resource);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public abstract boolean hasPermission(@NonNull Player source, @NonNull String permissionNode);
 | 
			
		||||
 | 
			
		||||
    public abstract boolean hasPermission(@NonNull CommandSourceStack source, @NonNull String permissionNode, int permissionLevel);
 | 
			
		||||
 | 
			
		||||
    @SuppressWarnings("BooleanMethodIsAlwaysInverted")
 | 
			
		||||
    private boolean loadConfig() {
 | 
			
		||||
        try {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,17 +25,18 @@
 | 
			
		|||
 | 
			
		||||
package org.geysermc.geyser.platform.mod;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.commands.CommandSourceStack;
 | 
			
		||||
import net.minecraft.world.entity.player.Player;
 | 
			
		||||
import org.geysermc.geyser.Constants;
 | 
			
		||||
import org.geysermc.geyser.platform.mod.command.ModCommandSender;
 | 
			
		||||
import org.geysermc.geyser.Permissions;
 | 
			
		||||
import org.geysermc.geyser.platform.mod.command.ModCommandSource;
 | 
			
		||||
import org.geysermc.geyser.util.VersionCheckUtils;
 | 
			
		||||
 | 
			
		||||
public final class GeyserModUpdateListener {
 | 
			
		||||
    public static void onPlayReady(Player player) {
 | 
			
		||||
        CommandSourceStack stack = player.createCommandSourceStack();
 | 
			
		||||
        if (GeyserModBootstrap.getInstance().hasPermission(stack, Constants.UPDATE_PERMISSION, 2)) {
 | 
			
		||||
            VersionCheckUtils.checkForGeyserUpdate(() -> new ModCommandSender(stack));
 | 
			
		||||
        // Should be creating this in the supplier, but we need it for the permission check.
 | 
			
		||||
        // Not a big deal currently because ModCommandSource doesn't load locale, so don't need to try to wait for it.
 | 
			
		||||
        ModCommandSource source = new ModCommandSource(player.createCommandSourceStack());
 | 
			
		||||
        if (source.hasPermission(Permissions.CHECK_UPDATE)) {
 | 
			
		||||
            VersionCheckUtils.checkForGeyserUpdate(() -> source);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,75 +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.platform.mod.command;
 | 
			
		||||
 | 
			
		||||
import com.mojang.brigadier.Command;
 | 
			
		||||
import com.mojang.brigadier.context.CommandContext;
 | 
			
		||||
import net.minecraft.commands.CommandSourceStack;
 | 
			
		||||
import org.geysermc.geyser.GeyserImpl;
 | 
			
		||||
import org.geysermc.geyser.command.GeyserCommand;
 | 
			
		||||
import org.geysermc.geyser.command.GeyserCommandExecutor;
 | 
			
		||||
import org.geysermc.geyser.platform.mod.GeyserModBootstrap;
 | 
			
		||||
import org.geysermc.geyser.session.GeyserSession;
 | 
			
		||||
import org.geysermc.geyser.text.ChatColor;
 | 
			
		||||
import org.geysermc.geyser.text.GeyserLocale;
 | 
			
		||||
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
 | 
			
		||||
public class GeyserModCommandExecutor extends GeyserCommandExecutor implements Command<CommandSourceStack> {
 | 
			
		||||
    private final GeyserCommand command;
 | 
			
		||||
 | 
			
		||||
    public GeyserModCommandExecutor(GeyserImpl geyser, GeyserCommand command) {
 | 
			
		||||
        super(geyser, Collections.singletonMap(command.name(), command));
 | 
			
		||||
        this.command = command;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public boolean testPermission(CommandSourceStack source) {
 | 
			
		||||
        return GeyserModBootstrap.getInstance().hasPermission(source, command.permission(), command.isSuggestedOpOnly() ? 2 : 0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int run(CommandContext<CommandSourceStack> context) {
 | 
			
		||||
        return runWithArgs(context, "");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public int runWithArgs(CommandContext<CommandSourceStack> context, String args) {
 | 
			
		||||
        CommandSourceStack source = context.getSource();
 | 
			
		||||
        ModCommandSender sender = new ModCommandSender(source);
 | 
			
		||||
        GeyserSession session = getGeyserSession(sender);
 | 
			
		||||
        if (!testPermission(source)) {
 | 
			
		||||
            sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.locale()));
 | 
			
		||||
            return 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (command.isBedrockOnly() && session == null) {
 | 
			
		||||
            sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", sender.locale()));
 | 
			
		||||
            return 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        command.execute(session, sender, args.split(" "));
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -31,19 +31,21 @@ import net.minecraft.core.RegistryAccess;
 | 
			
		|||
import net.minecraft.network.chat.Component;
 | 
			
		||||
import net.minecraft.server.level.ServerPlayer;
 | 
			
		||||
import org.checkerframework.checker.nullness.qual.NonNull;
 | 
			
		||||
import org.checkerframework.checker.nullness.qual.Nullable;
 | 
			
		||||
import org.geysermc.geyser.GeyserImpl;
 | 
			
		||||
import org.geysermc.geyser.command.GeyserCommandSource;
 | 
			
		||||
import org.geysermc.geyser.platform.mod.GeyserModBootstrap;
 | 
			
		||||
import org.geysermc.geyser.text.ChatColor;
 | 
			
		||||
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
 | 
			
		||||
public class ModCommandSender implements GeyserCommandSource {
 | 
			
		||||
public class ModCommandSource implements GeyserCommandSource {
 | 
			
		||||
 | 
			
		||||
    private final CommandSourceStack source;
 | 
			
		||||
 | 
			
		||||
    public ModCommandSender(CommandSourceStack source) {
 | 
			
		||||
    public ModCommandSource(CommandSourceStack source) {
 | 
			
		||||
        this.source = source;
 | 
			
		||||
        // todo find locale?
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
| 
						 | 
				
			
			@ -75,8 +77,24 @@ public class ModCommandSender implements GeyserCommandSource {
 | 
			
		|||
        return !(source.getEntity() instanceof ServerPlayer);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public @Nullable UUID playerUuid() {
 | 
			
		||||
        if (source.getEntity() instanceof ServerPlayer player) {
 | 
			
		||||
            return player.getUUID();
 | 
			
		||||
        }
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean hasPermission(String permission) {
 | 
			
		||||
        return GeyserModBootstrap.getInstance().hasPermission(source, permission, source.getServer().getOperatorUserPermissionLevel());
 | 
			
		||||
        // Unlike other bootstraps; we delegate to cloud here too:
 | 
			
		||||
        // On NeoForge; we'd have to keep track of all PermissionNodes - cloud already does that
 | 
			
		||||
        // For Fabric, we won't need to include the Fabric Permissions API anymore - cloud already does that too :p
 | 
			
		||||
        return GeyserImpl.getInstance().commandRegistry().hasPermission(this, permission);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Object handle() {
 | 
			
		||||
        return source;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -48,7 +48,6 @@ import org.checkerframework.checker.nullness.qual.NonNull;
 | 
			
		|||
import org.cloudburstmc.math.vector.Vector3i;
 | 
			
		||||
import org.geysermc.geyser.level.GeyserWorldManager;
 | 
			
		||||
import org.geysermc.geyser.network.GameProtocol;
 | 
			
		||||
import org.geysermc.geyser.platform.mod.GeyserModBootstrap;
 | 
			
		||||
import org.geysermc.geyser.session.GeyserSession;
 | 
			
		||||
import org.geysermc.geyser.util.MinecraftKey;
 | 
			
		||||
import org.geysermc.mcprotocollib.protocol.data.game.Holder;
 | 
			
		||||
| 
						 | 
				
			
			@ -111,12 +110,6 @@ public class GeyserModWorldManager extends GeyserWorldManager {
 | 
			
		|||
        return SharedConstants.getCurrentVersion().getProtocolVersion() == GameProtocol.getJavaProtocolVersion();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean hasPermission(GeyserSession session, String permission) {
 | 
			
		||||
        ServerPlayer player = getPlayer(session);
 | 
			
		||||
        return GeyserModBootstrap.getInstance().hasPermission(player, permission);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public GameMode getDefaultGameMode(GeyserSession session) {
 | 
			
		||||
        return GameMode.byId(server.getDefaultGameType().getId());
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,12 +17,12 @@ dependencies {
 | 
			
		|||
        classifier("all") // otherwise the unshaded jar is used without the shaded NMS implementations
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    implementation(libs.cloud.paper)
 | 
			
		||||
    implementation(libs.commodore)
 | 
			
		||||
 | 
			
		||||
    implementation(libs.adventure.text.serializer.bungeecord)
 | 
			
		||||
 | 
			
		||||
    compileOnly(libs.folia.api)
 | 
			
		||||
    compileOnly(libs.paper.mojangapi)
 | 
			
		||||
 | 
			
		||||
    compileOnlyApi(libs.viaversion)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -33,13 +33,15 @@ platformRelocate("com.fasterxml.jackson")
 | 
			
		|||
platformRelocate("net.kyori", "net.kyori.adventure.text.logger.slf4j.ComponentLogger")
 | 
			
		||||
platformRelocate("org.objectweb.asm")
 | 
			
		||||
platformRelocate("me.lucko.commodore")
 | 
			
		||||
platformRelocate("org.incendo")
 | 
			
		||||
platformRelocate("io.leangen.geantyref") // provided by cloud, should also be relocated
 | 
			
		||||
platformRelocate("org.yaml") // Broken as of 1.20
 | 
			
		||||
 | 
			
		||||
// These dependencies are already present on the platform
 | 
			
		||||
provided(libs.viaversion)
 | 
			
		||||
 | 
			
		||||
application {
 | 
			
		||||
    mainClass.set("org.geysermc.geyser.platform.spigot.GeyserSpigotMain")
 | 
			
		||||
tasks.withType<Jar> {
 | 
			
		||||
    manifest.attributes["Main-Class"] = "org.geysermc.geyser.platform.spigot.GeyserSpigotMain"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -30,37 +30,34 @@ import com.viaversion.viaversion.api.data.MappingData;
 | 
			
		|||
import com.viaversion.viaversion.api.protocol.ProtocolPathEntry;
 | 
			
		||||
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
 | 
			
		||||
import io.netty.buffer.ByteBuf;
 | 
			
		||||
import me.lucko.commodore.CommodoreProvider;
 | 
			
		||||
import org.bukkit.Bukkit;
 | 
			
		||||
import org.bukkit.block.data.BlockData;
 | 
			
		||||
import org.bukkit.command.CommandMap;
 | 
			
		||||
import org.bukkit.command.PluginCommand;
 | 
			
		||||
import org.bukkit.command.CommandSender;
 | 
			
		||||
import org.bukkit.event.EventHandler;
 | 
			
		||||
import org.bukkit.event.Listener;
 | 
			
		||||
import org.bukkit.event.server.ServerLoadEvent;
 | 
			
		||||
import org.bukkit.permissions.Permission;
 | 
			
		||||
import org.bukkit.permissions.PermissionDefault;
 | 
			
		||||
import org.bukkit.plugin.Plugin;
 | 
			
		||||
import org.bukkit.plugin.PluginManager;
 | 
			
		||||
import org.bukkit.plugin.java.JavaPlugin;
 | 
			
		||||
import org.checkerframework.checker.nullness.qual.NonNull;
 | 
			
		||||
import org.geysermc.geyser.Constants;
 | 
			
		||||
import org.geysermc.geyser.GeyserBootstrap;
 | 
			
		||||
import org.geysermc.geyser.GeyserImpl;
 | 
			
		||||
import org.geysermc.geyser.adapters.paper.PaperAdapters;
 | 
			
		||||
import org.geysermc.geyser.adapters.spigot.SpigotAdapters;
 | 
			
		||||
import org.geysermc.geyser.api.command.Command;
 | 
			
		||||
import org.geysermc.geyser.api.extension.Extension;
 | 
			
		||||
import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent;
 | 
			
		||||
import org.geysermc.geyser.api.util.PlatformType;
 | 
			
		||||
import org.geysermc.geyser.command.GeyserCommandManager;
 | 
			
		||||
import org.geysermc.geyser.command.CommandRegistry;
 | 
			
		||||
import org.geysermc.geyser.command.CommandSourceConverter;
 | 
			
		||||
import org.geysermc.geyser.command.GeyserCommandSource;
 | 
			
		||||
import org.geysermc.geyser.configuration.GeyserConfiguration;
 | 
			
		||||
import org.geysermc.geyser.dump.BootstrapDumpInfo;
 | 
			
		||||
import org.geysermc.geyser.level.WorldManager;
 | 
			
		||||
import org.geysermc.geyser.network.GameProtocol;
 | 
			
		||||
import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough;
 | 
			
		||||
import org.geysermc.geyser.ping.IGeyserPingPassthrough;
 | 
			
		||||
import org.geysermc.geyser.platform.spigot.command.GeyserBrigadierSupport;
 | 
			
		||||
import org.geysermc.geyser.platform.spigot.command.GeyserSpigotCommandExecutor;
 | 
			
		||||
import org.geysermc.geyser.platform.spigot.command.GeyserSpigotCommandManager;
 | 
			
		||||
import org.geysermc.geyser.platform.spigot.command.SpigotCommandRegistry;
 | 
			
		||||
import org.geysermc.geyser.platform.spigot.command.SpigotCommandSource;
 | 
			
		||||
import org.geysermc.geyser.platform.spigot.world.GeyserPistonListener;
 | 
			
		||||
import org.geysermc.geyser.platform.spigot.world.GeyserSpigotBlockPlaceListener;
 | 
			
		||||
import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotLegacyNativeWorldManager;
 | 
			
		||||
| 
						 | 
				
			
			@ -68,21 +65,21 @@ import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotNativeWorld
 | 
			
		|||
import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotWorldManager;
 | 
			
		||||
import org.geysermc.geyser.text.GeyserLocale;
 | 
			
		||||
import org.geysermc.geyser.util.FileUtils;
 | 
			
		||||
import org.incendo.cloud.bukkit.BukkitCommandManager;
 | 
			
		||||
import org.incendo.cloud.execution.ExecutionCoordinator;
 | 
			
		||||
import org.incendo.cloud.paper.LegacyPaperCommandManager;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.lang.reflect.Constructor;
 | 
			
		||||
import java.lang.reflect.InvocationTargetException;
 | 
			
		||||
import java.net.SocketAddress;
 | 
			
		||||
import java.nio.file.Path;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
 | 
			
		||||
public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
 | 
			
		||||
 | 
			
		||||
    private GeyserSpigotCommandManager geyserCommandManager;
 | 
			
		||||
    private CommandRegistry commandRegistry;
 | 
			
		||||
    private GeyserSpigotConfiguration geyserConfig;
 | 
			
		||||
    private GeyserSpigotInjector geyserInjector;
 | 
			
		||||
    private final GeyserSpigotLogger geyserLogger = GeyserPaperLogger.supported() ?
 | 
			
		||||
| 
						 | 
				
			
			@ -165,31 +162,37 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
 | 
			
		|||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onEnable() {
 | 
			
		||||
        this.geyserCommandManager = new GeyserSpigotCommandManager(geyser);
 | 
			
		||||
        this.geyserCommandManager.init();
 | 
			
		||||
 | 
			
		||||
        // Because Bukkit locks its command map upon startup, we need to
 | 
			
		||||
        // add our plugin commands in onEnable, but populating the executor
 | 
			
		||||
        // can happen at any time (later in #onGeyserEnable())
 | 
			
		||||
        CommandMap commandMap = GeyserSpigotCommandManager.getCommandMap();
 | 
			
		||||
        for (Extension extension : this.geyserCommandManager.extensionCommands().keySet()) {
 | 
			
		||||
            // Thanks again, Bukkit
 | 
			
		||||
            try {
 | 
			
		||||
                Constructor<PluginCommand> constructor = PluginCommand.class.getDeclaredConstructor(String.class, Plugin.class);
 | 
			
		||||
                constructor.setAccessible(true);
 | 
			
		||||
 | 
			
		||||
                PluginCommand pluginCommand = constructor.newInstance(extension.description().id(), this);
 | 
			
		||||
                pluginCommand.setDescription("The main command for the " + extension.name() + " Geyser extension!");
 | 
			
		||||
 | 
			
		||||
                commandMap.register(extension.description().id(), "geyserext", pluginCommand);
 | 
			
		||||
            } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException ex) {
 | 
			
		||||
                this.geyserLogger.error("Failed to construct PluginCommand for extension " + extension.name(), ex);
 | 
			
		||||
            }
 | 
			
		||||
        // Create command manager early so we can add Geyser extension commands
 | 
			
		||||
        var sourceConverter = new CommandSourceConverter<>(
 | 
			
		||||
                CommandSender.class,
 | 
			
		||||
                Bukkit::getPlayer,
 | 
			
		||||
                Bukkit::getConsoleSender,
 | 
			
		||||
                SpigotCommandSource::new
 | 
			
		||||
        );
 | 
			
		||||
        LegacyPaperCommandManager<GeyserCommandSource> cloud;
 | 
			
		||||
        try {
 | 
			
		||||
            // LegacyPaperCommandManager works for spigot too, see https://cloud.incendo.org/minecraft/paper
 | 
			
		||||
            cloud = new LegacyPaperCommandManager<>(
 | 
			
		||||
                    this,
 | 
			
		||||
                    ExecutionCoordinator.simpleCoordinator(),
 | 
			
		||||
                    sourceConverter
 | 
			
		||||
            );
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            throw new RuntimeException(e);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            // Commodore brigadier on Spigot/Paper 1.13 - 1.18.2
 | 
			
		||||
            // Paper-only brigadier on 1.19+
 | 
			
		||||
            cloud.registerBrigadier();
 | 
			
		||||
        } catch (BukkitCommandManager.BrigadierInitializationException e) {
 | 
			
		||||
            geyserLogger.debug("Failed to initialize Brigadier support: " + e.getMessage());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.commandRegistry = new SpigotCommandRegistry(geyser, cloud);
 | 
			
		||||
 | 
			
		||||
        // Needs to be an anonymous inner class otherwise Bukkit complains about missing classes
 | 
			
		||||
        Bukkit.getPluginManager().registerEvents(new Listener() {
 | 
			
		||||
 | 
			
		||||
            @EventHandler
 | 
			
		||||
            public void onServerLoaded(ServerLoadEvent event) {
 | 
			
		||||
                if (event.getType() == ServerLoadEvent.LoadType.RELOAD) {
 | 
			
		||||
| 
						 | 
				
			
			@ -227,7 +230,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
 | 
			
		|||
        }
 | 
			
		||||
        geyserLogger.debug("Spigot ping passthrough type: " + (this.geyserSpigotPingPassthrough == null ? null : this.geyserSpigotPingPassthrough.getClass()));
 | 
			
		||||
 | 
			
		||||
        // Don't need to re-create the world manager/re-register commands/reinject when reloading
 | 
			
		||||
        // Don't need to re-create the world manager/reinject when reloading
 | 
			
		||||
        if (GeyserImpl.getInstance().isReloading()) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -282,79 +285,40 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
 | 
			
		|||
            geyserLogger.debug("Using default world manager.");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        PluginCommand geyserCommand = this.getCommand("geyser");
 | 
			
		||||
        Objects.requireNonNull(geyserCommand, "base command cannot be null");
 | 
			
		||||
        geyserCommand.setExecutor(new GeyserSpigotCommandExecutor(geyser, geyserCommandManager.getCommands()));
 | 
			
		||||
 | 
			
		||||
        for (Map.Entry<Extension, Map<String, Command>> entry : this.geyserCommandManager.extensionCommands().entrySet()) {
 | 
			
		||||
            Map<String, Command> commands = entry.getValue();
 | 
			
		||||
            if (commands.isEmpty()) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            PluginCommand command = this.getCommand(entry.getKey().description().id());
 | 
			
		||||
            if (command == null) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            command.setExecutor(new GeyserSpigotCommandExecutor(this.geyser, commands));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Register permissions so they appear in, for example, LuckPerms' UI
 | 
			
		||||
        // Re-registering permissions throws an error
 | 
			
		||||
        for (Map.Entry<String, Command> entry : geyserCommandManager.commands().entrySet()) {
 | 
			
		||||
            Command command = entry.getValue();
 | 
			
		||||
            if (command.aliases().contains(entry.getKey())) {
 | 
			
		||||
                // Don't register aliases
 | 
			
		||||
                continue;
 | 
			
		||||
        // Re-registering permissions without removing it throws an error
 | 
			
		||||
        PluginManager pluginManager = Bukkit.getPluginManager();
 | 
			
		||||
        geyser.eventBus().fire((GeyserRegisterPermissionsEvent) (permission, def) -> {
 | 
			
		||||
            Objects.requireNonNull(permission, "permission");
 | 
			
		||||
            Objects.requireNonNull(def, "permission default for " + permission);
 | 
			
		||||
 | 
			
		||||
            if (permission.isBlank()) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            PermissionDefault permissionDefault = switch (def) {
 | 
			
		||||
                case TRUE -> PermissionDefault.TRUE;
 | 
			
		||||
                case FALSE -> PermissionDefault.FALSE;
 | 
			
		||||
                case NOT_SET -> PermissionDefault.OP;
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            Permission existingPermission = pluginManager.getPermission(permission);
 | 
			
		||||
            if (existingPermission != null) {
 | 
			
		||||
                geyserLogger.debug("permission " + permission + " with default " +
 | 
			
		||||
                        existingPermission.getDefault() + " is being overridden by " + permissionDefault);
 | 
			
		||||
 | 
			
		||||
                pluginManager.removePermission(permission);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Bukkit.getPluginManager().addPermission(new Permission(command.permission(),
 | 
			
		||||
                    GeyserLocale.getLocaleStringLog(command.description()),
 | 
			
		||||
                    command.isSuggestedOpOnly() ? PermissionDefault.OP : PermissionDefault.TRUE));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Register permissions for extension commands
 | 
			
		||||
        for (Map.Entry<Extension, Map<String, Command>> commandEntry : this.geyserCommandManager.extensionCommands().entrySet()) {
 | 
			
		||||
            for (Map.Entry<String, Command> entry : commandEntry.getValue().entrySet()) {
 | 
			
		||||
                Command command = entry.getValue();
 | 
			
		||||
                if (command.aliases().contains(entry.getKey())) {
 | 
			
		||||
                    // Don't register aliases
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (command.permission().isBlank()) {
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Avoid registering the same permission twice, e.g. for the extension help commands
 | 
			
		||||
                if (Bukkit.getPluginManager().getPermission(command.permission()) != null) {
 | 
			
		||||
                    GeyserImpl.getInstance().getLogger().debug("Skipping permission " + command.permission() + " as it is already registered");
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                Bukkit.getPluginManager().addPermission(new Permission(command.permission(),
 | 
			
		||||
                        GeyserLocale.getLocaleStringLog(command.description()),
 | 
			
		||||
                        command.isSuggestedOpOnly() ? PermissionDefault.OP : PermissionDefault.TRUE));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Bukkit.getPluginManager().addPermission(new Permission(Constants.UPDATE_PERMISSION,
 | 
			
		||||
                "Whether update notifications can be seen", PermissionDefault.OP));
 | 
			
		||||
            pluginManager.addPermission(new Permission(permission, permissionDefault));
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Events cannot be unregistered - re-registering results in duplicate firings
 | 
			
		||||
        GeyserSpigotBlockPlaceListener blockPlaceListener = new GeyserSpigotBlockPlaceListener(geyser, this.geyserWorldManager);
 | 
			
		||||
        Bukkit.getServer().getPluginManager().registerEvents(blockPlaceListener, this);
 | 
			
		||||
        pluginManager.registerEvents(blockPlaceListener, this);
 | 
			
		||||
 | 
			
		||||
        Bukkit.getServer().getPluginManager().registerEvents(new GeyserPistonListener(geyser, this.geyserWorldManager), this);
 | 
			
		||||
        pluginManager.registerEvents(new GeyserPistonListener(geyser, this.geyserWorldManager), this);
 | 
			
		||||
 | 
			
		||||
        Bukkit.getServer().getPluginManager().registerEvents(new GeyserSpigotUpdateListener(), this);
 | 
			
		||||
 | 
			
		||||
        boolean brigadierSupported = CommodoreProvider.isSupported();
 | 
			
		||||
        geyserLogger.debug("Brigadier supported? " + brigadierSupported);
 | 
			
		||||
        if (brigadierSupported) {
 | 
			
		||||
            GeyserBrigadierSupport.loadBrigadier(this, geyserCommand);
 | 
			
		||||
        }
 | 
			
		||||
        pluginManager.registerEvents(new GeyserSpigotUpdateListener(), this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
| 
						 | 
				
			
			@ -390,8 +354,8 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public GeyserCommandManager getGeyserCommandManager() {
 | 
			
		||||
        return this.geyserCommandManager;
 | 
			
		||||
    public CommandRegistry getCommandRegistry() {
 | 
			
		||||
        return this.commandRegistry;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -29,8 +29,8 @@ import org.bukkit.entity.Player;
 | 
			
		|||
import org.bukkit.event.EventHandler;
 | 
			
		||||
import org.bukkit.event.Listener;
 | 
			
		||||
import org.bukkit.event.player.PlayerJoinEvent;
 | 
			
		||||
import org.geysermc.geyser.Constants;
 | 
			
		||||
import org.geysermc.geyser.GeyserImpl;
 | 
			
		||||
import org.geysermc.geyser.Permissions;
 | 
			
		||||
import org.geysermc.geyser.platform.spigot.command.SpigotCommandSource;
 | 
			
		||||
import org.geysermc.geyser.util.VersionCheckUtils;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -40,7 +40,7 @@ public final class GeyserSpigotUpdateListener implements Listener {
 | 
			
		|||
    public void onPlayerJoin(final PlayerJoinEvent event) {
 | 
			
		||||
        if (GeyserImpl.getInstance().getConfig().isNotifyOnNewBedrockUpdate()) {
 | 
			
		||||
            final Player player = event.getPlayer();
 | 
			
		||||
            if (player.hasPermission(Constants.UPDATE_PERMISSION)) {
 | 
			
		||||
            if (player.hasPermission(Permissions.CHECK_UPDATE)) {
 | 
			
		||||
                VersionCheckUtils.checkForGeyserUpdate(() -> new SpigotCommandSource(player));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,61 +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.platform.spigot.command;
 | 
			
		||||
 | 
			
		||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
 | 
			
		||||
import me.lucko.commodore.Commodore;
 | 
			
		||||
import me.lucko.commodore.CommodoreProvider;
 | 
			
		||||
import org.bukkit.Bukkit;
 | 
			
		||||
import org.bukkit.command.PluginCommand;
 | 
			
		||||
import org.geysermc.geyser.platform.spigot.GeyserSpigotPlugin;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Needs to be a separate class so pre-1.13 loads correctly.
 | 
			
		||||
 */
 | 
			
		||||
public final class GeyserBrigadierSupport {
 | 
			
		||||
 | 
			
		||||
    public static void loadBrigadier(GeyserSpigotPlugin plugin, PluginCommand pluginCommand) {
 | 
			
		||||
        // Enable command completions if supported
 | 
			
		||||
        // This is beneficial because this is sent over the network and Bedrock can see it
 | 
			
		||||
        Commodore commodore = CommodoreProvider.getCommodore(plugin);
 | 
			
		||||
        LiteralArgumentBuilder<?> builder = LiteralArgumentBuilder.literal("geyser");
 | 
			
		||||
        for (String command : plugin.getGeyserCommandManager().getCommands().keySet()) {
 | 
			
		||||
            builder.then(LiteralArgumentBuilder.literal(command));
 | 
			
		||||
        }
 | 
			
		||||
        commodore.register(pluginCommand, builder);
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            Class.forName("com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent");
 | 
			
		||||
            Bukkit.getServer().getPluginManager().registerEvents(new GeyserPaperCommandListener(), plugin);
 | 
			
		||||
            plugin.getGeyserLogger().debug("Successfully registered AsyncPlayerSendCommandsEvent listener.");
 | 
			
		||||
        } catch (ClassNotFoundException e) {
 | 
			
		||||
            plugin.getGeyserLogger().debug("Not registering AsyncPlayerSendCommandsEvent listener.");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private GeyserBrigadierSupport() {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,87 +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.platform.spigot.command;
 | 
			
		||||
 | 
			
		||||
import com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent;
 | 
			
		||||
import com.mojang.brigadier.tree.CommandNode;
 | 
			
		||||
import org.bukkit.entity.Player;
 | 
			
		||||
import org.bukkit.event.EventHandler;
 | 
			
		||||
import org.bukkit.event.Listener;
 | 
			
		||||
import org.geysermc.geyser.GeyserImpl;
 | 
			
		||||
import org.geysermc.geyser.api.command.Command;
 | 
			
		||||
 | 
			
		||||
import java.net.InetSocketAddress;
 | 
			
		||||
import java.util.Iterator;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
public final class GeyserPaperCommandListener implements Listener {
 | 
			
		||||
 | 
			
		||||
    @SuppressWarnings("UnstableApiUsage")
 | 
			
		||||
    @EventHandler
 | 
			
		||||
    public void onCommandSend(AsyncPlayerSendCommandsEvent<?> event) {
 | 
			
		||||
        // Documentation says to check (event.isAsynchronous() || !event.hasFiredAsync()), but as of Paper 1.18.2
 | 
			
		||||
        // event.hasFiredAsync is never true
 | 
			
		||||
        if (event.isAsynchronous()) {
 | 
			
		||||
            CommandNode<?> geyserBrigadier = event.getCommandNode().getChild("geyser");
 | 
			
		||||
            if (geyserBrigadier != null) {
 | 
			
		||||
                Player player = event.getPlayer();
 | 
			
		||||
                boolean isJavaPlayer = isProbablyJavaPlayer(player);
 | 
			
		||||
                Map<String, Command> commands = GeyserImpl.getInstance().commandManager().getCommands();
 | 
			
		||||
                Iterator<? extends CommandNode<?>> it = geyserBrigadier.getChildren().iterator();
 | 
			
		||||
 | 
			
		||||
                while (it.hasNext()) {
 | 
			
		||||
                    CommandNode<?> subnode = it.next();
 | 
			
		||||
                    Command command = commands.get(subnode.getName());
 | 
			
		||||
                    if (command != null) {
 | 
			
		||||
                        if ((command.isBedrockOnly() && isJavaPlayer) || !player.hasPermission(command.permission())) {
 | 
			
		||||
                            // Remove this from the node as we don't have permission to use it
 | 
			
		||||
                            it.remove();
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * This early on, there is a rare chance that Geyser has yet to process the connection. We'll try to minimize that
 | 
			
		||||
     * chance, though.
 | 
			
		||||
     */
 | 
			
		||||
    private boolean isProbablyJavaPlayer(Player player) {
 | 
			
		||||
        if (GeyserImpl.getInstance().connectionByUuid(player.getUniqueId()) != null) {
 | 
			
		||||
            // For sure this is a Bedrock player
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (GeyserImpl.getInstance().getConfig().isUseDirectConnection()) {
 | 
			
		||||
            InetSocketAddress address = player.getAddress();
 | 
			
		||||
            if (address != null) {
 | 
			
		||||
                return address.getPort() != 0;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,88 +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.platform.spigot.command;
 | 
			
		||||
 | 
			
		||||
import org.bukkit.ChatColor;
 | 
			
		||||
import org.bukkit.command.Command;
 | 
			
		||||
import org.bukkit.command.CommandSender;
 | 
			
		||||
import org.bukkit.command.TabExecutor;
 | 
			
		||||
import org.checkerframework.checker.nullness.qual.NonNull;
 | 
			
		||||
import org.geysermc.geyser.GeyserImpl;
 | 
			
		||||
import org.geysermc.geyser.command.GeyserCommand;
 | 
			
		||||
import org.geysermc.geyser.command.GeyserCommandExecutor;
 | 
			
		||||
import org.geysermc.geyser.session.GeyserSession;
 | 
			
		||||
import org.geysermc.geyser.text.GeyserLocale;
 | 
			
		||||
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
public class GeyserSpigotCommandExecutor extends GeyserCommandExecutor implements TabExecutor {
 | 
			
		||||
 | 
			
		||||
    public GeyserSpigotCommandExecutor(GeyserImpl geyser, Map<String, org.geysermc.geyser.api.command.Command> commands) {
 | 
			
		||||
        super(geyser, commands);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean onCommand(@NonNull CommandSender sender, @NonNull Command command, @NonNull String label, String[] args) {
 | 
			
		||||
        SpigotCommandSource commandSender = new SpigotCommandSource(sender);
 | 
			
		||||
        GeyserSession session = getGeyserSession(commandSender);
 | 
			
		||||
 | 
			
		||||
        if (args.length > 0) {
 | 
			
		||||
            GeyserCommand geyserCommand = getCommand(args[0]);
 | 
			
		||||
            if (geyserCommand != null) {
 | 
			
		||||
                if (!sender.hasPermission(geyserCommand.permission())) {
 | 
			
		||||
                    String message = GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", commandSender.locale());
 | 
			
		||||
 | 
			
		||||
                    commandSender.sendMessage(ChatColor.RED + message);
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
                if (geyserCommand.isBedrockOnly() && session == null) {
 | 
			
		||||
                    sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", commandSender.locale()));
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
                geyserCommand.execute(session, commandSender, args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]);
 | 
			
		||||
                return true;
 | 
			
		||||
            } else {
 | 
			
		||||
                String message = GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.not_found", commandSender.locale());
 | 
			
		||||
                commandSender.sendMessage(ChatColor.RED + message);
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            getCommand("help").execute(session, commandSender, new String[0]);
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public List<String> onTabComplete(@NonNull CommandSender sender, @NonNull Command command, @NonNull String label, String[] args) {
 | 
			
		||||
        if (args.length == 1) {
 | 
			
		||||
            return tabComplete(new SpigotCommandSource(sender));
 | 
			
		||||
        }
 | 
			
		||||
        return Collections.emptyList();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
 | 
			
		||||
 * 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
 | 
			
		||||
| 
						 | 
				
			
			@ -29,16 +29,21 @@ import org.bukkit.Bukkit;
 | 
			
		|||
import org.bukkit.Server;
 | 
			
		||||
import org.bukkit.command.Command;
 | 
			
		||||
import org.bukkit.command.CommandMap;
 | 
			
		||||
import org.checkerframework.checker.nullness.qual.NonNull;
 | 
			
		||||
import org.geysermc.geyser.GeyserImpl;
 | 
			
		||||
import org.geysermc.geyser.command.GeyserCommandManager;
 | 
			
		||||
import org.geysermc.geyser.command.CommandRegistry;
 | 
			
		||||
import org.geysermc.geyser.command.GeyserCommandSource;
 | 
			
		||||
import org.incendo.cloud.CommandManager;
 | 
			
		||||
 | 
			
		||||
import java.lang.reflect.Field;
 | 
			
		||||
 | 
			
		||||
public class GeyserSpigotCommandManager extends GeyserCommandManager {
 | 
			
		||||
public class SpigotCommandRegistry extends CommandRegistry {
 | 
			
		||||
 | 
			
		||||
    private static final CommandMap COMMAND_MAP;
 | 
			
		||||
    private final CommandMap commandMap;
 | 
			
		||||
 | 
			
		||||
    public SpigotCommandRegistry(GeyserImpl geyser, CommandManager<GeyserCommandSource> cloud) {
 | 
			
		||||
        super(geyser, cloud);
 | 
			
		||||
 | 
			
		||||
    static {
 | 
			
		||||
        CommandMap commandMap = null;
 | 
			
		||||
        try {
 | 
			
		||||
            // Paper-only
 | 
			
		||||
| 
						 | 
				
			
			@ -49,24 +54,28 @@ public class GeyserSpigotCommandManager extends GeyserCommandManager {
 | 
			
		|||
                Field cmdMapField = Bukkit.getServer().getClass().getDeclaredField("commandMap");
 | 
			
		||||
                cmdMapField.setAccessible(true);
 | 
			
		||||
                commandMap = (CommandMap) cmdMapField.get(Bukkit.getServer());
 | 
			
		||||
            } catch (NoSuchFieldException | IllegalAccessException ex) {
 | 
			
		||||
                ex.printStackTrace();
 | 
			
		||||
            } catch (Exception ex) {
 | 
			
		||||
                geyser.getLogger().error("Failed to get Spigot's CommandMap", ex);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        COMMAND_MAP = commandMap;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public GeyserSpigotCommandManager(GeyserImpl geyser) {
 | 
			
		||||
        super(geyser);
 | 
			
		||||
        this.commandMap = commandMap;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @NonNull
 | 
			
		||||
    @Override
 | 
			
		||||
    public String description(String command) {
 | 
			
		||||
        Command cmd = COMMAND_MAP.getCommand(command.replace("/", ""));
 | 
			
		||||
        return cmd != null ? cmd.getDescription() : "";
 | 
			
		||||
    }
 | 
			
		||||
    public String description(@NonNull String command, @NonNull String locale) {
 | 
			
		||||
        // check if the command is /geyser or an extension command so that we can localize the description
 | 
			
		||||
        String description = super.description(command, locale);
 | 
			
		||||
        if (!description.isBlank()) {
 | 
			
		||||
            return description;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    public static CommandMap getCommandMap() {
 | 
			
		||||
        return COMMAND_MAP;
 | 
			
		||||
        if (commandMap != null) {
 | 
			
		||||
            Command cmd = commandMap.getCommand(command);
 | 
			
		||||
            if (cmd != null) {
 | 
			
		||||
                return cmd.getDescription();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return "";
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -27,17 +27,21 @@ package org.geysermc.geyser.platform.spigot.command;
 | 
			
		|||
 | 
			
		||||
import net.kyori.adventure.text.Component;
 | 
			
		||||
import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer;
 | 
			
		||||
import org.bukkit.command.CommandSender;
 | 
			
		||||
import org.bukkit.command.ConsoleCommandSender;
 | 
			
		||||
import org.bukkit.entity.Player;
 | 
			
		||||
import org.checkerframework.checker.nullness.qual.NonNull;
 | 
			
		||||
import org.checkerframework.checker.nullness.qual.Nullable;
 | 
			
		||||
import org.geysermc.geyser.command.GeyserCommandSource;
 | 
			
		||||
import org.geysermc.geyser.platform.spigot.PaperAdventure;
 | 
			
		||||
import org.geysermc.geyser.text.GeyserLocale;
 | 
			
		||||
 | 
			
		||||
public class SpigotCommandSource implements GeyserCommandSource {
 | 
			
		||||
    private final org.bukkit.command.CommandSender handle;
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
 | 
			
		||||
    public SpigotCommandSource(org.bukkit.command.CommandSender handle) {
 | 
			
		||||
public class SpigotCommandSource implements GeyserCommandSource {
 | 
			
		||||
    private final CommandSender handle;
 | 
			
		||||
 | 
			
		||||
    public SpigotCommandSource(CommandSender handle) {
 | 
			
		||||
        this.handle = handle;
 | 
			
		||||
        // Ensure even Java players' languages are loaded
 | 
			
		||||
        GeyserLocale.loadGeyserLocale(locale());
 | 
			
		||||
| 
						 | 
				
			
			@ -65,11 +69,24 @@ public class SpigotCommandSource implements GeyserCommandSource {
 | 
			
		|||
        handle.spigot().sendMessage(BungeeComponentSerializer.get().serialize(message));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Object handle() {
 | 
			
		||||
        return handle;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean isConsole() {
 | 
			
		||||
        return handle instanceof ConsoleCommandSender;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public @Nullable UUID playerUuid() {
 | 
			
		||||
        if (handle instanceof Player player) {
 | 
			
		||||
            return player.getUniqueId();
 | 
			
		||||
        }
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @SuppressWarnings("deprecation")
 | 
			
		||||
    @Override
 | 
			
		||||
    public String locale() {
 | 
			
		||||
| 
						 | 
				
			
			@ -83,6 +100,7 @@ public class SpigotCommandSource implements GeyserCommandSource {
 | 
			
		|||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean hasPermission(String permission) {
 | 
			
		||||
        return handle.hasPermission(permission);
 | 
			
		||||
        // Don't trust Spigot to handle blank permissions
 | 
			
		||||
        return permission.isBlank() || handle.hasPermission(permission);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -128,15 +128,6 @@ public class GeyserSpigotWorldManager extends WorldManager {
 | 
			
		|||
        return GameMode.byId(Bukkit.getDefaultGameMode().ordinal());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean hasPermission(GeyserSession session, String permission) {
 | 
			
		||||
        Player player = Bukkit.getPlayer(session.javaUuid());
 | 
			
		||||
        if (player != null) {
 | 
			
		||||
            return player.hasPermission(permission);
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public @NonNull CompletableFuture<@Nullable DataComponents> getPickItemComponents(GeyserSession session, int x, int y, int z, boolean addNbtData) {
 | 
			
		||||
        Player bukkitPlayer;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,11 +6,3 @@ version: ${version}
 | 
			
		|||
softdepend: ["ViaVersion", "floodgate"]
 | 
			
		||||
api-version: 1.13
 | 
			
		||||
folia-supported: true
 | 
			
		||||
commands:
 | 
			
		||||
  geyser:
 | 
			
		||||
    description: The main command for Geyser.
 | 
			
		||||
    usage: /geyser <subcommand>
 | 
			
		||||
    permission: geyser.command
 | 
			
		||||
permissions:
 | 
			
		||||
  geyser.command:
 | 
			
		||||
    default: true
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,9 @@
 | 
			
		|||
import com.github.jengelman.gradle.plugins.shadow.transformers.Log4j2PluginsCacheFileTransformer
 | 
			
		||||
 | 
			
		||||
plugins {
 | 
			
		||||
    application
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
val terminalConsoleVersion = "1.2.0"
 | 
			
		||||
val jlineVersion = "3.21.0"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -42,7 +42,8 @@ import org.checkerframework.checker.nullness.qual.NonNull;
 | 
			
		|||
import org.geysermc.geyser.GeyserBootstrap;
 | 
			
		||||
import org.geysermc.geyser.GeyserImpl;
 | 
			
		||||
import org.geysermc.geyser.api.util.PlatformType;
 | 
			
		||||
import org.geysermc.geyser.command.GeyserCommandManager;
 | 
			
		||||
import org.geysermc.geyser.command.CommandRegistry;
 | 
			
		||||
import org.geysermc.geyser.command.standalone.StandaloneCloudCommandManager;
 | 
			
		||||
import org.geysermc.geyser.configuration.GeyserConfiguration;
 | 
			
		||||
import org.geysermc.geyser.configuration.GeyserJacksonConfiguration;
 | 
			
		||||
import org.geysermc.geyser.dump.BootstrapDumpInfo;
 | 
			
		||||
| 
						 | 
				
			
			@ -69,7 +70,8 @@ import java.util.stream.Collectors;
 | 
			
		|||
 | 
			
		||||
public class GeyserStandaloneBootstrap implements GeyserBootstrap {
 | 
			
		||||
 | 
			
		||||
    private GeyserCommandManager geyserCommandManager;
 | 
			
		||||
    private StandaloneCloudCommandManager cloud;
 | 
			
		||||
    private CommandRegistry commandRegistry;
 | 
			
		||||
    private GeyserStandaloneConfiguration geyserConfig;
 | 
			
		||||
    private final GeyserStandaloneLogger geyserLogger = new GeyserStandaloneLogger();
 | 
			
		||||
    private IGeyserPingPassthrough geyserPingPassthrough;
 | 
			
		||||
| 
						 | 
				
			
			@ -222,13 +224,24 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
 | 
			
		|||
 | 
			
		||||
        geyser = GeyserImpl.load(PlatformType.STANDALONE, this);
 | 
			
		||||
 | 
			
		||||
        geyserCommandManager = new GeyserCommandManager(geyser);
 | 
			
		||||
        geyserCommandManager.init();
 | 
			
		||||
        boolean reloading = geyser.isReloading();
 | 
			
		||||
        if (!reloading) {
 | 
			
		||||
            // Currently there would be no significant benefit of re-initializing commands. Also, we would have to unsubscribe CommandRegistry.
 | 
			
		||||
            // Fire GeyserDefineCommandsEvent after PreInitEvent, before PostInitEvent, for consistency with other bootstraps.
 | 
			
		||||
            cloud = new StandaloneCloudCommandManager(geyser);
 | 
			
		||||
            commandRegistry = new CommandRegistry(geyser, cloud);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        GeyserImpl.start();
 | 
			
		||||
 | 
			
		||||
        if (!reloading) {
 | 
			
		||||
            // Event must be fired after CommandRegistry has subscribed its listener.
 | 
			
		||||
            // Also, the subscription for the Permissions class is created when Geyser is initialized.
 | 
			
		||||
            cloud.fireRegisterPermissionsEvent();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (gui != null) {
 | 
			
		||||
            gui.enableCommands(geyser.getScheduledThread(), geyserCommandManager);
 | 
			
		||||
            gui.enableCommands(geyser.getScheduledThread(), commandRegistry);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser);
 | 
			
		||||
| 
						 | 
				
			
			@ -255,8 +268,6 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
 | 
			
		|||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onGeyserDisable() {
 | 
			
		||||
        // We can re-register commands on standalone, so why not
 | 
			
		||||
        GeyserImpl.getInstance().commandManager().getCommands().clear();
 | 
			
		||||
        geyser.disable();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -277,8 +288,8 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public GeyserCommandManager getGeyserCommandManager() {
 | 
			
		||||
        return geyserCommandManager;
 | 
			
		||||
    public CommandRegistry getCommandRegistry() {
 | 
			
		||||
        return commandRegistry;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -44,7 +44,9 @@ public class GeyserStandaloneLogger extends SimpleTerminalConsole implements Gey
 | 
			
		|||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void runCommand(String line) {
 | 
			
		||||
        GeyserImpl.getInstance().commandManager().runCommand(this, line);
 | 
			
		||||
        // don't block the terminal!
 | 
			
		||||
        GeyserImpl geyser = GeyserImpl.getInstance();
 | 
			
		||||
        geyser.getScheduledThread().execute(() -> geyser.commandRegistry().runCommand(this, line));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -28,7 +28,7 @@ package org.geysermc.geyser.platform.standalone.gui;
 | 
			
		|||
import org.checkerframework.checker.nullness.qual.NonNull;
 | 
			
		||||
import org.geysermc.geyser.GeyserImpl;
 | 
			
		||||
import org.geysermc.geyser.GeyserLogger;
 | 
			
		||||
import org.geysermc.geyser.command.GeyserCommandManager;
 | 
			
		||||
import org.geysermc.geyser.command.CommandRegistry;
 | 
			
		||||
import org.geysermc.geyser.session.GeyserSession;
 | 
			
		||||
import org.geysermc.geyser.text.GeyserLocale;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -271,15 +271,14 @@ public class GeyserStandaloneGUI {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Enable the command input box.
 | 
			
		||||
     * Enables the command input box.
 | 
			
		||||
     *
 | 
			
		||||
     * @param executor the executor for running commands off the GUI thread
 | 
			
		||||
     * @param commandManager the command manager to delegate commands to
 | 
			
		||||
     * @param executor the executor that commands will be run on
 | 
			
		||||
     * @param registry the command registry containing all current commands
 | 
			
		||||
     */
 | 
			
		||||
    public void enableCommands(ScheduledExecutorService executor, GeyserCommandManager commandManager) {
 | 
			
		||||
    public void enableCommands(ScheduledExecutorService executor, CommandRegistry registry) {
 | 
			
		||||
        // we don't want to block the GUI thread with the command execution
 | 
			
		||||
        // todo: once cloud is used, an AsynchronousCommandExecutionCoordinator can be used to avoid this scheduler
 | 
			
		||||
        commandListener.handler = cmd -> executor.schedule(() -> commandManager.runCommand(logger, cmd), 0, TimeUnit.SECONDS);
 | 
			
		||||
        commandListener.dispatcher = cmd -> executor.execute(() -> registry.runCommand(logger, cmd));
 | 
			
		||||
        commandInput.setEnabled(true);
 | 
			
		||||
        commandInput.requestFocusInWindow();
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -344,13 +343,14 @@ public class GeyserStandaloneGUI {
 | 
			
		|||
 | 
			
		||||
    private class CommandListener implements ActionListener {
 | 
			
		||||
 | 
			
		||||
        private Consumer<String> handler;
 | 
			
		||||
        private Consumer<String> dispatcher;
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void actionPerformed(ActionEvent e) {
 | 
			
		||||
            String command = commandInput.getText();
 | 
			
		||||
            // the headless variant of Standalone strips trailing whitespace for us - we need to manually
 | 
			
		||||
            String command = commandInput.getText().stripTrailing();
 | 
			
		||||
            appendConsole(command + "\n"); // show what was run in the console
 | 
			
		||||
            handler.accept(command); // run the command
 | 
			
		||||
            dispatcher.accept(command); // run the command
 | 
			
		||||
            commandInput.setText(""); // clear the input
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,12 +3,15 @@ dependencies {
 | 
			
		|||
    api(projects.core)
 | 
			
		||||
 | 
			
		||||
    compileOnlyApi(libs.velocity.api)
 | 
			
		||||
    api(libs.cloud.velocity)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
platformRelocate("com.fasterxml.jackson")
 | 
			
		||||
platformRelocate("it.unimi.dsi.fastutil")
 | 
			
		||||
platformRelocate("net.kyori.adventure.text.serializer.gson.legacyimpl")
 | 
			
		||||
platformRelocate("org.yaml")
 | 
			
		||||
platformRelocate("org.incendo")
 | 
			
		||||
platformRelocate("io.leangen.geantyref") // provided by cloud, should also be relocated
 | 
			
		||||
 | 
			
		||||
exclude("com.google.*:*")
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -38,8 +41,8 @@ exclude("net.kyori:adventure-nbt:*")
 | 
			
		|||
// These dependencies are already present on the platform
 | 
			
		||||
provided(libs.velocity.api)
 | 
			
		||||
 | 
			
		||||
application {
 | 
			
		||||
    mainClass.set("org.geysermc.geyser.platform.velocity.GeyserVelocityMain")
 | 
			
		||||
tasks.withType<Jar> {
 | 
			
		||||
    manifest.attributes["Main-Class"] = "org.geysermc.geyser.platform.velocity.GeyserVelocityMain"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
 | 
			
		||||
| 
						 | 
				
			
			@ -74,4 +77,4 @@ tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
 | 
			
		|||
modrinth {
 | 
			
		||||
    uploadFile.set(tasks.getByPath("shadowJar"))
 | 
			
		||||
    loaders.addAll("velocity")
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,7 +26,7 @@
 | 
			
		|||
package org.geysermc.geyser.platform.velocity;
 | 
			
		||||
 | 
			
		||||
import com.google.inject.Inject;
 | 
			
		||||
import com.velocitypowered.api.command.CommandManager;
 | 
			
		||||
import com.velocitypowered.api.command.CommandSource;
 | 
			
		||||
import com.velocitypowered.api.event.Subscribe;
 | 
			
		||||
import com.velocitypowered.api.event.proxy.ListenerBoundEvent;
 | 
			
		||||
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
 | 
			
		||||
| 
						 | 
				
			
			@ -34,24 +34,28 @@ import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
 | 
			
		|||
import com.velocitypowered.api.network.ListenerType;
 | 
			
		||||
import com.velocitypowered.api.network.ProtocolVersion;
 | 
			
		||||
import com.velocitypowered.api.plugin.Plugin;
 | 
			
		||||
import com.velocitypowered.api.plugin.PluginContainer;
 | 
			
		||||
import com.velocitypowered.api.proxy.ProxyServer;
 | 
			
		||||
import lombok.Getter;
 | 
			
		||||
import org.checkerframework.checker.nullness.qual.NonNull;
 | 
			
		||||
import org.checkerframework.checker.nullness.qual.Nullable;
 | 
			
		||||
import org.geysermc.geyser.GeyserBootstrap;
 | 
			
		||||
import org.geysermc.geyser.GeyserImpl;
 | 
			
		||||
import org.geysermc.geyser.api.command.Command;
 | 
			
		||||
import org.geysermc.geyser.api.extension.Extension;
 | 
			
		||||
import org.geysermc.geyser.api.util.PlatformType;
 | 
			
		||||
import org.geysermc.geyser.command.GeyserCommandManager;
 | 
			
		||||
import org.geysermc.geyser.command.CommandRegistry;
 | 
			
		||||
import org.geysermc.geyser.command.CommandSourceConverter;
 | 
			
		||||
import org.geysermc.geyser.command.GeyserCommandSource;
 | 
			
		||||
import org.geysermc.geyser.configuration.GeyserConfiguration;
 | 
			
		||||
import org.geysermc.geyser.dump.BootstrapDumpInfo;
 | 
			
		||||
import org.geysermc.geyser.network.GameProtocol;
 | 
			
		||||
import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough;
 | 
			
		||||
import org.geysermc.geyser.ping.IGeyserPingPassthrough;
 | 
			
		||||
import org.geysermc.geyser.platform.velocity.command.GeyserVelocityCommandExecutor;
 | 
			
		||||
import org.geysermc.geyser.platform.velocity.command.VelocityCommandSource;
 | 
			
		||||
import org.geysermc.geyser.text.GeyserLocale;
 | 
			
		||||
import org.geysermc.geyser.util.FileUtils;
 | 
			
		||||
import org.incendo.cloud.CommandManager;
 | 
			
		||||
import org.incendo.cloud.execution.ExecutionCoordinator;
 | 
			
		||||
import org.incendo.cloud.velocity.VelocityCommandManager;
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
| 
						 | 
				
			
			@ -59,29 +63,28 @@ import java.io.IOException;
 | 
			
		|||
import java.net.SocketAddress;
 | 
			
		||||
import java.nio.file.Path;
 | 
			
		||||
import java.nio.file.Paths;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
 | 
			
		||||
@Plugin(id = "geyser", name = GeyserImpl.NAME + "-Velocity", version = GeyserImpl.VERSION, url = "https://geysermc.org", authors = "GeyserMC")
 | 
			
		||||
public class GeyserVelocityPlugin implements GeyserBootstrap {
 | 
			
		||||
 | 
			
		||||
    private final ProxyServer proxyServer;
 | 
			
		||||
    private final CommandManager commandManager;
 | 
			
		||||
    private final PluginContainer container;
 | 
			
		||||
    private final GeyserVelocityLogger geyserLogger;
 | 
			
		||||
    private GeyserCommandManager geyserCommandManager;
 | 
			
		||||
    private GeyserVelocityConfiguration geyserConfig;
 | 
			
		||||
    private GeyserVelocityInjector geyserInjector;
 | 
			
		||||
    private IGeyserPingPassthrough geyserPingPassthrough;
 | 
			
		||||
    private CommandRegistry commandRegistry;
 | 
			
		||||
    private GeyserImpl geyser;
 | 
			
		||||
 | 
			
		||||
    @Getter
 | 
			
		||||
    private final Path configFolder = Paths.get("plugins/" + GeyserImpl.NAME + "-Velocity/");
 | 
			
		||||
 | 
			
		||||
    @Inject
 | 
			
		||||
    public GeyserVelocityPlugin(ProxyServer server, Logger logger, CommandManager manager) {
 | 
			
		||||
        this.geyserLogger = new GeyserVelocityLogger(logger);
 | 
			
		||||
    public GeyserVelocityPlugin(ProxyServer server, PluginContainer container, Logger logger) {
 | 
			
		||||
        this.proxyServer = server;
 | 
			
		||||
        this.commandManager = manager;
 | 
			
		||||
        this.container = container;
 | 
			
		||||
        this.geyserLogger = new GeyserVelocityLogger(logger);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
| 
						 | 
				
			
			@ -117,8 +120,19 @@ public class GeyserVelocityPlugin implements GeyserBootstrap {
 | 
			
		|||
            this.geyserLogger.setDebug(geyserConfig.isDebugMode());
 | 
			
		||||
            GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
 | 
			
		||||
        } else {
 | 
			
		||||
            this.geyserCommandManager = new GeyserCommandManager(geyser);
 | 
			
		||||
            this.geyserCommandManager.init();
 | 
			
		||||
            var sourceConverter = new CommandSourceConverter<>(
 | 
			
		||||
                    CommandSource.class,
 | 
			
		||||
                    id -> proxyServer.getPlayer(id).orElse(null),
 | 
			
		||||
                    proxyServer::getConsoleCommandSource,
 | 
			
		||||
                    VelocityCommandSource::new
 | 
			
		||||
            );
 | 
			
		||||
            CommandManager<GeyserCommandSource> cloud = new VelocityCommandManager<>(
 | 
			
		||||
                    container,
 | 
			
		||||
                    proxyServer,
 | 
			
		||||
                    ExecutionCoordinator.simpleCoordinator(),
 | 
			
		||||
                    sourceConverter
 | 
			
		||||
            );
 | 
			
		||||
            this.commandRegistry = new CommandRegistry(geyser, cloud, false); // applying root permission would be a breaking change because we can't register permission defaults
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        GeyserImpl.start();
 | 
			
		||||
| 
						 | 
				
			
			@ -129,22 +143,10 @@ public class GeyserVelocityPlugin implements GeyserBootstrap {
 | 
			
		|||
            this.geyserPingPassthrough = new GeyserVelocityPingPassthrough(proxyServer);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // No need to re-register commands when reloading
 | 
			
		||||
        if (GeyserImpl.getInstance().isReloading()) {
 | 
			
		||||
            return;
 | 
			
		||||
        // No need to re-register events
 | 
			
		||||
        if (!GeyserImpl.getInstance().isReloading()) {
 | 
			
		||||
            proxyServer.getEventManager().register(this, new GeyserVelocityUpdateListener());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.commandManager.register("geyser", new GeyserVelocityCommandExecutor(geyser, geyserCommandManager.getCommands()));
 | 
			
		||||
        for (Map.Entry<Extension, Map<String, Command>> entry : this.geyserCommandManager.extensionCommands().entrySet()) {
 | 
			
		||||
            Map<String, Command> commands = entry.getValue();
 | 
			
		||||
            if (commands.isEmpty()) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.commandManager.register(entry.getKey().description().id(), new GeyserVelocityCommandExecutor(this.geyser, commands));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        proxyServer.getEventManager().register(this, new GeyserVelocityUpdateListener());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
| 
						 | 
				
			
			@ -175,8 +177,8 @@ public class GeyserVelocityPlugin implements GeyserBootstrap {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public GeyserCommandManager getGeyserCommandManager() {
 | 
			
		||||
        return this.geyserCommandManager;
 | 
			
		||||
    public CommandRegistry getCommandRegistry() {
 | 
			
		||||
        return this.commandRegistry;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -28,8 +28,8 @@ package org.geysermc.geyser.platform.velocity;
 | 
			
		|||
import com.velocitypowered.api.event.Subscribe;
 | 
			
		||||
import com.velocitypowered.api.event.connection.PostLoginEvent;
 | 
			
		||||
import com.velocitypowered.api.proxy.Player;
 | 
			
		||||
import org.geysermc.geyser.Constants;
 | 
			
		||||
import org.geysermc.geyser.GeyserImpl;
 | 
			
		||||
import org.geysermc.geyser.Permissions;
 | 
			
		||||
import org.geysermc.geyser.platform.velocity.command.VelocityCommandSource;
 | 
			
		||||
import org.geysermc.geyser.util.VersionCheckUtils;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -39,7 +39,7 @@ public final class GeyserVelocityUpdateListener {
 | 
			
		|||
    public void onPlayerJoin(PostLoginEvent event) {
 | 
			
		||||
        if (GeyserImpl.getInstance().getConfig().isNotifyOnNewBedrockUpdate()) {
 | 
			
		||||
            final Player player = event.getPlayer();
 | 
			
		||||
            if (player.hasPermission(Constants.UPDATE_PERMISSION)) {
 | 
			
		||||
            if (player.hasPermission(Permissions.CHECK_UPDATE)) {
 | 
			
		||||
                VersionCheckUtils.checkForGeyserUpdate(() -> new VelocityCommandSource(player));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,83 +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.platform.velocity.command;
 | 
			
		||||
 | 
			
		||||
import com.velocitypowered.api.command.SimpleCommand;
 | 
			
		||||
import org.geysermc.geyser.GeyserImpl;
 | 
			
		||||
import org.geysermc.geyser.api.command.Command;
 | 
			
		||||
import org.geysermc.geyser.command.GeyserCommand;
 | 
			
		||||
import org.geysermc.geyser.command.GeyserCommandExecutor;
 | 
			
		||||
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 java.util.Arrays;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
public class GeyserVelocityCommandExecutor extends GeyserCommandExecutor implements SimpleCommand {
 | 
			
		||||
 | 
			
		||||
    public GeyserVelocityCommandExecutor(GeyserImpl geyser, Map<String, Command> commands) {
 | 
			
		||||
        super(geyser, commands);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void execute(Invocation invocation) {
 | 
			
		||||
        GeyserCommandSource sender = new VelocityCommandSource(invocation.source());
 | 
			
		||||
        GeyserSession session = getGeyserSession(sender);
 | 
			
		||||
 | 
			
		||||
        if (invocation.arguments().length > 0) {
 | 
			
		||||
            GeyserCommand command = getCommand(invocation.arguments()[0]);
 | 
			
		||||
            if (command != null) {
 | 
			
		||||
                if (!invocation.source().hasPermission(getCommand(invocation.arguments()[0]).permission())) {
 | 
			
		||||
                    sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.locale()));
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                if (command.isBedrockOnly() && session == null) {
 | 
			
		||||
                    sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", sender.locale()));
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                command.execute(session, sender, invocation.arguments().length > 1 ? Arrays.copyOfRange(invocation.arguments(), 1, invocation.arguments().length) : new String[0]);
 | 
			
		||||
            } else {
 | 
			
		||||
                String message = GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.not_found", sender.locale());
 | 
			
		||||
                sender.sendMessage(ChatColor.RED + message);
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            getCommand("help").execute(session, sender, new String[0]);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public List<String> suggest(Invocation invocation) {
 | 
			
		||||
        // Velocity seems to do the splitting a bit differently. This results in the same behaviour in bungeecord/spigot.
 | 
			
		||||
        if (invocation.arguments().length == 0 || invocation.arguments().length == 1) {
 | 
			
		||||
            return tabComplete(new VelocityCommandSource(invocation.source()));
 | 
			
		||||
        }
 | 
			
		||||
        return Collections.emptyList();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -31,10 +31,12 @@ import com.velocitypowered.api.proxy.Player;
 | 
			
		|||
import net.kyori.adventure.text.Component;
 | 
			
		||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
 | 
			
		||||
import org.checkerframework.checker.nullness.qual.NonNull;
 | 
			
		||||
import org.checkerframework.checker.nullness.qual.Nullable;
 | 
			
		||||
import org.geysermc.geyser.command.GeyserCommandSource;
 | 
			
		||||
import org.geysermc.geyser.text.GeyserLocale;
 | 
			
		||||
 | 
			
		||||
import java.util.Locale;
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
 | 
			
		||||
public class VelocityCommandSource implements GeyserCommandSource {
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -72,6 +74,14 @@ public class VelocityCommandSource implements GeyserCommandSource {
 | 
			
		|||
        return handle instanceof ConsoleCommandSource;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public @Nullable UUID playerUuid() {
 | 
			
		||||
        if (handle instanceof Player player) {
 | 
			
		||||
            return player.getUniqueId();
 | 
			
		||||
        }
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public String locale() {
 | 
			
		||||
        if (handle instanceof Player) {
 | 
			
		||||
| 
						 | 
				
			
			@ -83,6 +93,12 @@ public class VelocityCommandSource implements GeyserCommandSource {
 | 
			
		|||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean hasPermission(String permission) {
 | 
			
		||||
        return handle.hasPermission(permission);
 | 
			
		||||
        // Handle blank permissions ourselves, as velocity only handles empty ones
 | 
			
		||||
        return permission.isBlank() || handle.hasPermission(permission);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Object handle() {
 | 
			
		||||
        return handle;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,12 +8,14 @@ platformRelocate("net.kyori")
 | 
			
		|||
platformRelocate("org.yaml")
 | 
			
		||||
platformRelocate("it.unimi.dsi.fastutil")
 | 
			
		||||
platformRelocate("org.cloudburstmc.netty")
 | 
			
		||||
platformRelocate("org.incendo")
 | 
			
		||||
platformRelocate("io.leangen.geantyref") // provided by cloud, should also be relocated
 | 
			
		||||
 | 
			
		||||
// These dependencies are already present on the platform
 | 
			
		||||
provided(libs.viaproxy)
 | 
			
		||||
 | 
			
		||||
application {
 | 
			
		||||
    mainClass.set("org.geysermc.geyser.platform.viaproxy.GeyserViaProxyMain")
 | 
			
		||||
tasks.withType<Jar> {
 | 
			
		||||
    manifest.attributes["Main-Class"] = "org.geysermc.geyser.platform.viaproxy.GeyserViaProxyMain"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -34,13 +34,15 @@ import net.raphimc.viaproxy.plugins.events.ProxyStartEvent;
 | 
			
		|||
import net.raphimc.viaproxy.plugins.events.ProxyStopEvent;
 | 
			
		||||
import net.raphimc.viaproxy.plugins.events.ShouldVerifyOnlineModeEvent;
 | 
			
		||||
import org.apache.logging.log4j.LogManager;
 | 
			
		||||
import org.checkerframework.checker.nullness.qual.NonNull;
 | 
			
		||||
import org.geysermc.geyser.GeyserBootstrap;
 | 
			
		||||
import org.geysermc.geyser.GeyserImpl;
 | 
			
		||||
import org.geysermc.geyser.GeyserLogger;
 | 
			
		||||
import org.geysermc.geyser.api.event.EventRegistrar;
 | 
			
		||||
import org.geysermc.geyser.api.network.AuthType;
 | 
			
		||||
import org.geysermc.geyser.api.util.PlatformType;
 | 
			
		||||
import org.geysermc.geyser.command.GeyserCommandManager;
 | 
			
		||||
import org.geysermc.geyser.command.CommandRegistry;
 | 
			
		||||
import org.geysermc.geyser.command.standalone.StandaloneCloudCommandManager;
 | 
			
		||||
import org.geysermc.geyser.configuration.GeyserConfiguration;
 | 
			
		||||
import org.geysermc.geyser.dump.BootstrapDumpInfo;
 | 
			
		||||
import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough;
 | 
			
		||||
| 
						 | 
				
			
			@ -50,7 +52,6 @@ import org.geysermc.geyser.session.GeyserSession;
 | 
			
		|||
import org.geysermc.geyser.text.GeyserLocale;
 | 
			
		||||
import org.geysermc.geyser.util.FileUtils;
 | 
			
		||||
import org.geysermc.geyser.util.LoopbackUtil;
 | 
			
		||||
import org.jetbrains.annotations.NotNull;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
| 
						 | 
				
			
			@ -66,7 +67,8 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst
 | 
			
		|||
    private final GeyserViaProxyLogger logger = new GeyserViaProxyLogger(LogManager.getLogger("Geyser"));
 | 
			
		||||
    private GeyserViaProxyConfiguration config;
 | 
			
		||||
    private GeyserImpl geyser;
 | 
			
		||||
    private GeyserCommandManager commandManager;
 | 
			
		||||
    private StandaloneCloudCommandManager cloud;
 | 
			
		||||
    private CommandRegistry commandRegistry;
 | 
			
		||||
    private IGeyserPingPassthrough pingPassthrough;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
| 
						 | 
				
			
			@ -87,7 +89,9 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst
 | 
			
		|||
    @EventHandler
 | 
			
		||||
    private void onConsoleCommand(final ConsoleCommandEvent event) {
 | 
			
		||||
        final String command = event.getCommand().startsWith("/") ? event.getCommand().substring(1) : event.getCommand();
 | 
			
		||||
        if (this.getGeyserCommandManager().runCommand(this.getGeyserLogger(), command + " " + String.join(" ", event.getArgs()))) {
 | 
			
		||||
        CommandRegistry registry = this.getCommandRegistry();
 | 
			
		||||
        if (registry.rootCommands().contains(command)) {
 | 
			
		||||
            registry.runCommand(this.getGeyserLogger(), command + " " + String.join(" ", event.getArgs()));
 | 
			
		||||
            event.setCancelled(true);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -128,17 +132,25 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst
 | 
			
		|||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onGeyserEnable() {
 | 
			
		||||
        if (GeyserImpl.getInstance().isReloading()) {
 | 
			
		||||
        boolean reloading = geyser.isReloading();
 | 
			
		||||
        if (reloading) {
 | 
			
		||||
            if (!this.loadConfig()) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            // Only initialized once - documented in the Geyser-Standalone bootstrap
 | 
			
		||||
            this.cloud = new StandaloneCloudCommandManager(geyser);
 | 
			
		||||
            this.commandRegistry = new CommandRegistry(geyser, cloud);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.commandManager = new GeyserCommandManager(this.geyser);
 | 
			
		||||
        this.commandManager.init();
 | 
			
		||||
 | 
			
		||||
        GeyserImpl.start();
 | 
			
		||||
 | 
			
		||||
        if (!reloading) {
 | 
			
		||||
            // Event must be fired after CommandRegistry has subscribed its listener.
 | 
			
		||||
            // Also, the subscription for the Permissions class is created when Geyser is initialized (by GeyserImpl#start)
 | 
			
		||||
            this.cloud.fireRegisterPermissionsEvent();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (ViaProxy.getConfig().getTargetVersion() != null && ViaProxy.getConfig().getTargetVersion().newerThanOrEqualTo(LegacyProtocolVersion.b1_8tob1_8_1)) {
 | 
			
		||||
            // Only initialize the ping passthrough if the protocol version is above beta 1.7.3, as that's when the status protocol was added
 | 
			
		||||
            this.pingPassthrough = GeyserLegacyPingPassthrough.init(this.geyser);
 | 
			
		||||
| 
						 | 
				
			
			@ -166,8 +178,8 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public GeyserCommandManager getGeyserCommandManager() {
 | 
			
		||||
        return this.commandManager;
 | 
			
		||||
    public CommandRegistry getCommandRegistry() {
 | 
			
		||||
        return this.commandRegistry;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
| 
						 | 
				
			
			@ -185,7 +197,7 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst
 | 
			
		|||
        return new GeyserViaProxyDumpInfo();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @NotNull
 | 
			
		||||
    @NonNull
 | 
			
		||||
    @Override
 | 
			
		||||
    public String getServerBindAddress() {
 | 
			
		||||
        if (ViaProxy.getConfig().getBindAddress() instanceof InetSocketAddress socketAddress) {
 | 
			
		||||
| 
						 | 
				
			
			@ -209,6 +221,7 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst
 | 
			
		|||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @SuppressWarnings("BooleanMethodIsAlwaysInverted")
 | 
			
		||||
    private boolean loadConfig() {
 | 
			
		||||
        try {
 | 
			
		||||
            final File configFile = FileUtils.fileOrCopiedFromResource(new File(ROOT_FOLDER, "config.yml"), "config.yml", s -> s.replaceAll("generateduuid", UUID.randomUUID().toString()), this);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -37,6 +37,10 @@ provided("io.netty", "netty-resolver-dns")
 | 
			
		|||
provided("io.netty", "netty-resolver-dns-native-macos")
 | 
			
		||||
provided("org.ow2.asm", "asm")
 | 
			
		||||
 | 
			
		||||
// cloud-fabric/cloud-neoforge jij's all cloud depends already
 | 
			
		||||
provided("org.incendo", ".*")
 | 
			
		||||
provided("io.leangen.geantyref", "geantyref")
 | 
			
		||||
 | 
			
		||||
architectury {
 | 
			
		||||
    minecraft = libs.minecraft.get().version as String
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -120,4 +124,4 @@ repositories {
 | 
			
		|||
    maven("https://oss.sonatype.org/content/repositories/snapshots/")
 | 
			
		||||
    maven("https://s01.oss.sonatype.org/content/repositories/snapshots/")
 | 
			
		||||
    maven("https://maven.neoforged.net/releases")
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,3 @@
 | 
			
		|||
plugins {
 | 
			
		||||
    application
 | 
			
		||||
    id("geyser.publish-conventions")
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
 | 
			
		||||
 * 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
 | 
			
		||||
| 
						 | 
				
			
			@ -39,15 +39,19 @@ public enum DeviceOs {
 | 
			
		|||
    OSX("macOS"),
 | 
			
		||||
    AMAZON("Amazon"),
 | 
			
		||||
    GEARVR("Gear VR"),
 | 
			
		||||
    HOLOLENS("Hololens"),
 | 
			
		||||
    @Deprecated HOLOLENS("Hololens"),
 | 
			
		||||
    UWP("Windows"),
 | 
			
		||||
    WIN32("Windows x86"),
 | 
			
		||||
    DEDICATED("Dedicated"),
 | 
			
		||||
    TVOS("Apple TV"),
 | 
			
		||||
    PS4("PS4"),
 | 
			
		||||
    @Deprecated TVOS("Apple TV"),
 | 
			
		||||
    /**
 | 
			
		||||
     * This is for all PlayStation platforms not just PS4
 | 
			
		||||
     */
 | 
			
		||||
    PS4("PlayStation"),
 | 
			
		||||
    NX("Switch"),
 | 
			
		||||
    XBOX("Xbox One"),
 | 
			
		||||
    WINDOWS_PHONE("Windows Phone");
 | 
			
		||||
    XBOX("Xbox"),
 | 
			
		||||
    @Deprecated WINDOWS_PHONE("Windows Phone"),
 | 
			
		||||
    LINUX("Linux");
 | 
			
		||||
 | 
			
		||||
    private static final DeviceOs[] VALUES = values();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
Some files were not shown because too many files have changed in this diff Show more
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue