Mirror von
https://github.com/GeyserMC/Geyser.git
synchronisiert 2024-12-26 16:12:46 +01:00
Merge branch 'master' of https://github.com/GeyserMC/Geyser into feature/configurate
Dieser Commit ist enthalten in:
Commit
f7a677ec67
1
.github/workflows/build.yml
vendored
1
.github/workflows/build.yml
vendored
@ -16,7 +16,6 @@ on:
|
||||
- 'LICENSE'
|
||||
- 'Jenkinsfile '
|
||||
- 'README.md'
|
||||
- 'licenseheader.txt'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -249,6 +249,8 @@ locales/
|
||||
/packs/
|
||||
/dump.json
|
||||
/saved-refresh-tokens.json
|
||||
/saved-auth-chains.json
|
||||
/custom_mappings/
|
||||
/languages/
|
||||
/custom-skulls.yml
|
||||
/custom-skulls.yml
|
||||
/permissions.yml
|
||||
|
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2019-2023 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
|
||||
|
17
README.md
17
README.md
@ -14,16 +14,15 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t
|
||||
|
||||
Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have joined us here!
|
||||
|
||||
### Currently supporting Minecraft Bedrock 1.20.80 - 1.21.1 and Minecraft Java 1.21
|
||||
## Supported Versions
|
||||
Geyser is currently supporting Minecraft Bedrock 1.20.80 - 1.21.20 and Minecraft Java Server 1.21/1.21.1. For more info please see [here](https://geysermc.org/wiki/geyser/supported-versions/).
|
||||
|
||||
## Setting Up
|
||||
Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Geyser.
|
||||
|
||||
[![YouTube Video](https://img.youtube.com/vi/U7dZZ8w7Gi4/0.jpg)](https://www.youtube.com/watch?v=U7dZZ8w7Gi4)
|
||||
Take a look [here](https://geysermc.org/wiki/geyser/setup/) for how to set up Geyser.
|
||||
|
||||
## Links:
|
||||
- Website: https://geysermc.org
|
||||
- Docs: https://wiki.geysermc.org/geyser/
|
||||
- Docs: https://geysermc.org/wiki/geyser/
|
||||
- Download: https://geysermc.org/download
|
||||
- Discord: https://discord.gg/geysermc
|
||||
- Donate: https://opencollective.com/geysermc
|
||||
@ -34,7 +33,7 @@ Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Ge
|
||||
- Some Entity Flags
|
||||
|
||||
## What can't be fixed
|
||||
There are a few things Geyser is unable to support due to various differences between Minecraft Bedrock and Java. For a list of these limitations, see the [Current Limitations](https://wiki.geysermc.org/geyser/current-limitations/) page.
|
||||
There are a few things Geyser is unable to support due to various differences between Minecraft Bedrock and Java. For a list of these limitations, see the [Current Limitations](https://geysermc.org/wiki/geyser/current-limitations/) page.
|
||||
|
||||
## Compiling
|
||||
1. Clone the repo to your computer
|
||||
@ -42,12 +41,12 @@ There are a few things Geyser is unable to support due to various differences be
|
||||
3. Run `gradlew build` and locate to `bootstrap/build` folder.
|
||||
|
||||
## Contributing
|
||||
Any contributions are appreciated. Please feel free to reach out to us on [Discord](http://discord.geysermc.org/) if
|
||||
Any contributions are appreciated. Please feel free to reach out to us on [Discord](https://discord.gg/geysermc) if
|
||||
you're interested in helping out with Geyser.
|
||||
|
||||
## Libraries Used:
|
||||
- [Adventure Text Library](https://github.com/KyoriPowered/adventure)
|
||||
- [NukkitX Bedrock Protocol Library](https://github.com/NukkitX/Protocol)
|
||||
- [Steveice10's Java Protocol Library](https://github.com/Steveice10/MCProtocolLib)
|
||||
- [CloudburstMC Bedrock Protocol Library](https://github.com/CloudburstMC/Protocol)
|
||||
- [GeyserMC's Java Protocol Library](https://github.com/GeyserMC/MCProtocolLib)
|
||||
- [TerminalConsoleAppender](https://github.com/Minecrell/TerminalConsoleAppender)
|
||||
- [Simple Logging Facade for Java (slf4j)](https://github.com/qos-ch/slf4j)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
53
api/src/main/java-templates/org/geysermc/geyser/api/BuildData.java
Normale Datei
53
api/src/main/java-templates/org/geysermc/geyser/api/BuildData.java
Normale Datei
@ -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.
|
||||
*
|
||||
|
@ -60,6 +60,16 @@ public interface GeyserConnection extends Connection, CommandSource {
|
||||
*/
|
||||
@NonNull EntityData entities();
|
||||
|
||||
/**
|
||||
* Returns the current ping of the connection.
|
||||
*/
|
||||
int ping();
|
||||
|
||||
/**
|
||||
* Closes the currently open form on the client.
|
||||
*/
|
||||
void closeForm();
|
||||
|
||||
/**
|
||||
* @param javaId the Java entity ID to look up.
|
||||
* @return a {@link GeyserEntity} if present in this connection's entity tracker.
|
||||
|
@ -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)
|
||||
}
|
||||
@ -7,6 +9,8 @@ dependencies {
|
||||
platformRelocate("net.md_5.bungee.jni")
|
||||
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
|
||||
platformRelocate("org.spongepowered")
|
||||
platformRelocate("io.leangen.geantyref")
|
||||
@ -15,8 +19,8 @@ platformRelocate("org.bstats")
|
||||
// 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> {
|
||||
|
@ -36,6 +36,7 @@ import net.md_5.bungee.api.event.ProxyPingEvent;
|
||||
import net.md_5.bungee.api.plugin.Listener;
|
||||
import net.md_5.bungee.protocol.ProtocolConstants;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.ping.GeyserPingInfo;
|
||||
import org.geysermc.geyser.ping.IGeyserPingPassthrough;
|
||||
|
||||
@ -43,6 +44,7 @@ import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class GeyserBungeePingPassthrough implements IGeyserPingPassthrough, Listener {
|
||||
@ -59,7 +61,17 @@ public class GeyserBungeePingPassthrough implements IGeyserPingPassthrough, List
|
||||
future.complete(event);
|
||||
}
|
||||
}));
|
||||
ProxyPingEvent event = future.join();
|
||||
|
||||
ProxyPingEvent event;
|
||||
|
||||
try {
|
||||
event = future.get(100, TimeUnit.MILLISECONDS);
|
||||
} catch (Throwable cause) {
|
||||
String address = GeyserImpl.getInstance().config().logPlayerIpAddresses() ? inetSocketAddress.toString() : "<IP address withheld>";
|
||||
GeyserImpl.getInstance().getLogger().error("Failed to get ping information for " + address, cause);
|
||||
return null;
|
||||
}
|
||||
|
||||
ServerPing response = event.getResponse();
|
||||
return new GeyserPingInfo(
|
||||
response.getDescriptionComponent().toLegacyText(),
|
||||
|
@ -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;
|
||||
@ -35,17 +36,20 @@ import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.geyser.FloodgateKeyLoader;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.GeyserPluginBootstrap;
|
||||
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.configuration.ConfigLoader;
|
||||
import org.geysermc.geyser.configuration.GeyserPluginConfig;
|
||||
import org.geysermc.geyser.command.CommandRegistry;
|
||||
import org.geysermc.geyser.command.CommandSourceConverter;
|
||||
import org.geysermc.geyser.command.GeyserCommandSource;
|
||||
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.incendo.cloud.CommandManager;
|
||||
import org.incendo.cloud.bungee.BungeeCommandManager;
|
||||
import org.incendo.cloud.execution.ExecutionCoordinator;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@ -55,18 +59,16 @@ 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.concurrent.TimeUnit;
|
||||
|
||||
public class GeyserBungeePlugin extends Plugin implements GeyserPluginBootstrap {
|
||||
|
||||
private GeyserCommandManager geyserCommandManager;
|
||||
private CommandRegistry commandRegistry;
|
||||
private GeyserPluginConfig geyserConfig;
|
||||
private GeyserBungeeInjector geyserInjector;
|
||||
private final GeyserBungeeLogger geyserLogger = new GeyserBungeeLogger(getLogger());
|
||||
private IGeyserPingPassthrough geyserBungeePingPassthrough;
|
||||
|
||||
private GeyserImpl geyser;
|
||||
|
||||
@Override
|
||||
@ -98,10 +100,16 @@ public class GeyserBungeePlugin extends Plugin implements GeyserPluginBootstrap
|
||||
this.geyserLogger.setDebug(geyserConfig.debugMode());
|
||||
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 (geyser == null) {
|
||||
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
|
||||
@ -141,10 +149,18 @@ public class GeyserBungeePlugin extends Plugin implements GeyserPluginBootstrap
|
||||
}
|
||||
this.geyserLogger.setDebug(geyserConfig.debugMode());
|
||||
} 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
|
||||
@ -179,16 +195,6 @@ public class GeyserBungeePlugin extends Plugin implements GeyserPluginBootstrap
|
||||
}
|
||||
|
||||
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
|
||||
@ -224,8 +230,8 @@ public class GeyserBungeePlugin extends Plugin implements GeyserPluginBootstrap
|
||||
}
|
||||
|
||||
@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().config().notifyOnNewBedrockUpdate()) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
@ -16,7 +16,8 @@ afterEvaluate {
|
||||
dependencies {
|
||||
api(projects.core)
|
||||
compileOnly(libs.mixin)
|
||||
compileOnly(libs.mixinextras)
|
||||
|
||||
// Only here to suppress "unknown enum constant EnvType.CLIENT" warnings. DO NOT USE!
|
||||
compileOnly(libs.fabric.loader)
|
||||
}
|
||||
}
|
||||
|
@ -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.mcauthlib) { 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")
|
||||
|
@ -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,32 +25,23 @@
|
||||
|
||||
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.FloodgateKeyLoader;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.GeyserLogger;
|
||||
import org.geysermc.geyser.GeyserPluginBootstrap;
|
||||
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.configuration.ConfigLoader;
|
||||
import org.geysermc.geyser.configuration.GeyserPluginConfig;
|
||||
import org.geysermc.geyser.command.CommandRegistry;
|
||||
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;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public abstract class GeyserModBootstrap implements GeyserPluginBootstrap {
|
||||
@ -69,13 +59,15 @@ public abstract class GeyserModBootstrap implements GeyserPluginBootstrap {
|
||||
|
||||
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 GeyserPluginConfig geyserConfig;
|
||||
private GeyserModInjector geyserInjector;
|
||||
private final GeyserModLogger geyserLogger = new GeyserModLogger();
|
||||
@ -92,13 +84,14 @@ public abstract class GeyserModBootstrap implements GeyserPluginBootstrap {
|
||||
}
|
||||
this.geyserLogger.setDebug(geyserConfig.debugMode());
|
||||
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() {
|
||||
// "Disabling" a mod isn't possible; so if we fail to initialize we need to manually stop here
|
||||
if (geyser == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (GeyserImpl.getInstance().isReloading()) {
|
||||
if (!loadConfig()) {
|
||||
return;
|
||||
@ -127,50 +120,6 @@ public abstract class GeyserModBootstrap implements GeyserPluginBootstrap {
|
||||
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
|
||||
@ -203,8 +152,8 @@ public abstract class GeyserModBootstrap implements GeyserPluginBootstrap {
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeyserCommandManager getGeyserCommandManager() {
|
||||
return geyserCommandManager;
|
||||
public CommandRegistry getCommandRegistry() {
|
||||
return commandRegistry;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -232,6 +181,7 @@ public abstract class GeyserModBootstrap implements GeyserPluginBootstrap {
|
||||
return this.server.getServerVersion();
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions") // Certain IDEA installations think that ip cannot be null
|
||||
@NonNull
|
||||
@Override
|
||||
public String getServerBindAddress() {
|
||||
@ -274,10 +224,6 @@ public abstract class GeyserModBootstrap implements GeyserPluginBootstrap {
|
||||
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;
|
||||
}
|
||||
}
|
@ -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.mod.mixin.server;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.BlockItem;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.context.BlockPlaceContext;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.SoundType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.protocol.bedrock.data.SoundEvent;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.LevelSoundEventPacket;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
|
||||
|
||||
@Mixin(BlockItem.class)
|
||||
public class BlockPlaceMixin {
|
||||
|
||||
@Inject(method = "place", locals = LocalCapture.CAPTURE_FAILSOFT, at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;playSound(Lnet/minecraft/world/entity/player/Player;Lnet/minecraft/core/BlockPos;Lnet/minecraft/sounds/SoundEvent;Lnet/minecraft/sounds/SoundSource;FF)V"))
|
||||
private void geyser$hijackPlaySound(BlockPlaceContext blockPlaceContext, CallbackInfoReturnable<InteractionResult> cir, BlockPlaceContext blockPlaceContext2, BlockState blockState, BlockPos blockPos, Level level, Player player, ItemStack itemStack, BlockState blockState2, SoundType soundType) {
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
GeyserSession session = GeyserImpl.getInstance().connectionByUuid(player.getUUID());
|
||||
if (session == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3f position = Vector3f.from(
|
||||
blockPos.getX(),
|
||||
blockPos.getY(),
|
||||
blockPos.getZ()
|
||||
);
|
||||
|
||||
LevelSoundEventPacket placeBlockSoundPacket = new LevelSoundEventPacket();
|
||||
placeBlockSoundPacket.setSound(SoundEvent.PLACE);
|
||||
placeBlockSoundPacket.setPosition(position);
|
||||
placeBlockSoundPacket.setBabySound(false);
|
||||
placeBlockSoundPacket.setExtraData(session.getBlockMappings().getBedrockBlockId(Block.BLOCK_STATE_REGISTRY.getId(blockState2)));
|
||||
placeBlockSoundPacket.setIdentifier(":");
|
||||
session.sendUpstreamPacket(placeBlockSoundPacket);
|
||||
session.setLastBlockPlacePosition(null);
|
||||
session.setLastBlockPlaced(null);
|
||||
}
|
||||
}
|
@ -0,0 +1,127 @@
|
||||
/*
|
||||
* 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.mod.mixin.server;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
|
||||
import com.llamalad7.mixinextras.sugar.Share;
|
||||
import com.llamalad7.mixinextras.sugar.ref.LocalRef;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.piston.PistonBaseBlock;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.cloudburstmc.math.vector.Vector3i;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.session.cache.PistonCache;
|
||||
import org.geysermc.geyser.translator.level.block.entity.PistonBlockEntity;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.level.block.value.PistonValueType;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@Mixin(PistonBaseBlock.class)
|
||||
public class PistonBaseBlockMixin {
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private boolean isSticky;
|
||||
|
||||
@ModifyExpressionValue(method = "moveBlocks",
|
||||
at = @At(value = "INVOKE", target = "Lcom/google/common/collect/Maps;newHashMap()Ljava/util/HashMap;")
|
||||
)
|
||||
private HashMap<BlockPos, BlockState> geyser$onMapCreate(HashMap<BlockPos, BlockState> original, @Share("pushBlocks") LocalRef<Map<BlockPos, BlockState>> localRef) {
|
||||
localRef.set(original);
|
||||
return original;
|
||||
}
|
||||
|
||||
@Inject(method = "moveBlocks",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/piston/PistonStructureResolver;getToDestroy()Ljava/util/List;")
|
||||
)
|
||||
private void geyser$onBlocksMove(Level level, BlockPos blockPos, Direction direction, boolean isExtending, CallbackInfoReturnable<Boolean> cir, @Share("pushBlocks") LocalRef<Map<BlockPos, BlockState>> localRef) {
|
||||
PistonValueType type = isExtending ? PistonValueType.PUSHING : PistonValueType.PULLING;
|
||||
boolean sticky = this.isSticky;
|
||||
|
||||
Object2ObjectMap<Vector3i, org.geysermc.geyser.level.block.type.BlockState> attachedBlocks = new Object2ObjectArrayMap<>();
|
||||
boolean blocksFilled = false;
|
||||
|
||||
for (Map.Entry<UUID, GeyserSession> entry : GeyserImpl.getInstance().getSessionManager().getSessions().entrySet()) {
|
||||
Player player = level.getPlayerByUUID(entry.getKey());
|
||||
//noinspection resource
|
||||
if (player == null || !player.level().equals(level)) {
|
||||
continue;
|
||||
}
|
||||
GeyserSession session = entry.getValue();
|
||||
|
||||
int dX = Math.abs(blockPos.getX() - player.getBlockX()) >> 4;
|
||||
int dZ = Math.abs(blockPos.getZ() - player.getBlockZ()) >> 4;
|
||||
if ((dX * dX + dZ * dZ) > session.getServerRenderDistance() * session.getServerRenderDistance()) {
|
||||
// Ignore pistons outside the player's render distance
|
||||
continue;
|
||||
}
|
||||
|
||||
// Trying to grab the blocks from the world like other platforms would result in the moving piston block
|
||||
// being returned instead.
|
||||
if (!blocksFilled) {
|
||||
Map<BlockPos, net.minecraft.world.level.block.state.BlockState> blocks = localRef.get();
|
||||
for (Map.Entry<BlockPos, BlockState> blockStateEntry : blocks.entrySet()) {
|
||||
int blockStateId = Block.BLOCK_STATE_REGISTRY.getId(blockStateEntry.getValue());
|
||||
org.geysermc.geyser.level.block.type.BlockState state = org.geysermc.geyser.level.block.type.BlockState.of(blockStateId);
|
||||
attachedBlocks.put(geyser$fromBlockPos(blockStateEntry.getKey()), state);
|
||||
}
|
||||
blocksFilled = true;
|
||||
}
|
||||
|
||||
org.geysermc.geyser.level.physics.Direction orientation = org.geysermc.geyser.level.physics.Direction.VALUES[direction.ordinal()];
|
||||
|
||||
Vector3i position = geyser$fromBlockPos(blockPos);
|
||||
session.executeInEventLoop(() -> {
|
||||
PistonCache pistonCache = session.getPistonCache();
|
||||
PistonBlockEntity blockEntity = pistonCache.getPistons().computeIfAbsent(position, pos ->
|
||||
new PistonBlockEntity(session, position, orientation, sticky, !isExtending));
|
||||
blockEntity.setAction(type, attachedBlocks);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static Vector3i geyser$fromBlockPos(BlockPos pos) {
|
||||
return Vector3i.from(pos.getX(), pos.getY(), pos.getZ());
|
||||
}
|
||||
|
||||
}
|
@ -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());
|
||||
|
@ -4,6 +4,8 @@
|
||||
"package": "org.geysermc.geyser.platform.mod.mixin",
|
||||
"compatibilityLevel": "JAVA_17",
|
||||
"mixins": [
|
||||
"server.BlockPlaceMixin",
|
||||
"server.PistonBaseBlockMixin",
|
||||
"server.ServerConnectionListenerMixin"
|
||||
],
|
||||
"server": [
|
||||
|
@ -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)
|
||||
|
||||
@ -37,6 +37,8 @@ platformRelocate("it.unimi.dsi.fastutil")
|
||||
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
|
||||
platformRelocate("org.spongepowered")
|
||||
platformRelocate("io.leangen.geantyref")
|
||||
@ -46,8 +48,8 @@ platformRelocate("org.bstats")
|
||||
// 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> {
|
||||
@ -87,5 +89,7 @@ tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
|
||||
|
||||
modrinth {
|
||||
uploadFile.set(tasks.getByPath("shadowJar"))
|
||||
gameVersions.addAll("1.16.5", "1.17", "1.17.1", "1.18", "1.18.1", "1.18.2", "1.19",
|
||||
"1.19.1", "1.19.2", "1.19.3", "1.19.4", "1.20", "1.20.1", "1.20.2", "1.20.3", "1.20.4", "1.20.5", "1.20.6")
|
||||
loaders.addAll("spigot", "paper")
|
||||
}
|
||||
|
@ -30,29 +30,28 @@ 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.FloodgateKeyLoader;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.GeyserPluginBootstrap;
|
||||
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.ConfigLoader;
|
||||
import org.geysermc.geyser.configuration.GeyserPluginConfig;
|
||||
import org.geysermc.geyser.dump.BootstrapDumpInfo;
|
||||
@ -60,29 +59,28 @@ 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;
|
||||
import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotNativeWorldManager;
|
||||
import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotWorldManager;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
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;
|
||||
|
||||
public class GeyserSpigotPlugin extends JavaPlugin implements GeyserPluginBootstrap {
|
||||
|
||||
private GeyserSpigotCommandManager geyserCommandManager;
|
||||
private CommandRegistry commandRegistry;
|
||||
private GeyserPluginConfig geyserConfig;
|
||||
private GeyserSpigotInjector geyserInjector;
|
||||
private final GeyserSpigotLogger geyserLogger = GeyserPaperLogger.supported() ?
|
||||
@ -120,7 +118,6 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserPluginBootst
|
||||
geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server.message", "1.13.2"));
|
||||
geyserLogger.error("");
|
||||
geyserLogger.error("*********************************************");
|
||||
Bukkit.getPluginManager().disablePlugin(this);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -134,7 +131,6 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserPluginBootst
|
||||
geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server_type.message", "Paper"));
|
||||
geyserLogger.error("");
|
||||
geyserLogger.error("*********************************************");
|
||||
Bukkit.getPluginManager().disablePlugin(this);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -147,10 +143,25 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserPluginBootst
|
||||
geyserLogger.error("This version of Spigot is using an outdated version of netty. Please use Paper instead!");
|
||||
geyserLogger.error("");
|
||||
geyserLogger.error("*********************************************");
|
||||
Bukkit.getPluginManager().disablePlugin(this);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Check spigot config for BungeeCord mode
|
||||
if (Bukkit.getServer().spigot().getConfig().getBoolean("settings.bungeecord")) {
|
||||
warnInvalidProxySetups("BungeeCord");
|
||||
return;
|
||||
}
|
||||
|
||||
// Now: Check for velocity mode - deliberately after checking bungeecord because this is a paper only option
|
||||
if (Bukkit.getServer().spigot().getPaperConfig().getBoolean("proxies.velocity.enabled")) {
|
||||
warnInvalidProxySetups("Velocity");
|
||||
return;
|
||||
}
|
||||
} catch (NoSuchMethodError e) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
if (!loadConfig()) {
|
||||
return;
|
||||
}
|
||||
@ -164,31 +175,43 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserPluginBootst
|
||||
|
||||
@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);
|
||||
}
|
||||
// Disabling the plugin in onLoad() is not supported; we need to manually stop here and disable ourselves
|
||||
if (geyser == null) {
|
||||
Bukkit.getPluginManager().disablePlugin(this);
|
||||
return;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
@ -225,7 +248,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserPluginBootst
|
||||
}
|
||||
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;
|
||||
}
|
||||
@ -280,79 +303,40 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserPluginBootst
|
||||
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
|
||||
@ -388,8 +372,8 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserPluginBootst
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeyserCommandManager getGeyserCommandManager() {
|
||||
return this.geyserCommandManager;
|
||||
public CommandRegistry getCommandRegistry() {
|
||||
return this.commandRegistry;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -495,4 +479,13 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserPluginBootst
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void warnInvalidProxySetups(String platform) {
|
||||
geyserLogger.error("*********************************************");
|
||||
geyserLogger.error("");
|
||||
geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_proxy_backend", platform));
|
||||
geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.bootstrap.setup_guide", "https://geysermc.org/wiki/geyser/setup/"));
|
||||
geyserLogger.error("");
|
||||
geyserLogger.error("*********************************************");
|
||||
}
|
||||
}
|
||||
|
@ -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().config().notifyOnNewBedrockUpdate()) {
|
||||
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().config().asPluginConfig().orElseThrow().useDirectConnection()) {
|
||||
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"
|
||||
|
||||
|
@ -37,10 +37,11 @@ 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.configuration.ConfigLoader;
|
||||
import org.geysermc.geyser.configuration.GeyserConfig;
|
||||
import org.geysermc.geyser.configuration.GeyserRemoteConfig;
|
||||
import org.geysermc.geyser.command.CommandRegistry;
|
||||
import org.geysermc.geyser.command.standalone.StandaloneCloudCommandManager;
|
||||
import org.geysermc.geyser.dump.BootstrapDumpInfo;
|
||||
import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough;
|
||||
import org.geysermc.geyser.ping.IGeyserPingPassthrough;
|
||||
@ -62,7 +63,8 @@ import java.util.Map;
|
||||
|
||||
public class GeyserStandaloneBootstrap implements GeyserBootstrap {
|
||||
|
||||
private GeyserCommandManager geyserCommandManager;
|
||||
private StandaloneCloudCommandManager cloud;
|
||||
private CommandRegistry commandRegistry;
|
||||
private GeyserConfig geyserConfig;
|
||||
private final GeyserStandaloneLogger geyserLogger = new GeyserStandaloneLogger();
|
||||
private IGeyserPingPassthrough geyserPingPassthrough;
|
||||
@ -177,13 +179,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);
|
||||
@ -210,8 +223,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();
|
||||
}
|
||||
|
||||
@ -232,8 +243,8 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeyserCommandManager getGeyserCommandManager() {
|
||||
return geyserCommandManager;
|
||||
public CommandRegistry getCommandRegistry() {
|
||||
return commandRegistry;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -45,7 +45,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,14 +3,16 @@ dependencies {
|
||||
api(projects.core)
|
||||
|
||||
compileOnlyApi(libs.velocity.api)
|
||||
api(libs.cloud.velocity)
|
||||
}
|
||||
|
||||
platformRelocate("it.unimi.dsi.fastutil")
|
||||
platformRelocate("net.kyori.adventure.text.serializer.gson.legacyimpl")
|
||||
platformRelocate("org.yaml")
|
||||
platformRelocate("org.spongepowered")
|
||||
platformRelocate("io.leangen.geantyref")
|
||||
platformRelocate("org.bstats")
|
||||
platformRelocate("org.incendo")
|
||||
platformRelocate("io.leangen.geantyref") // provided by cloud and Configurate, should also be relocated
|
||||
|
||||
exclude("com.google.*:*")
|
||||
|
||||
@ -40,8 +42,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> {
|
||||
|
@ -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;
|
||||
@ -45,44 +45,49 @@ import org.geysermc.geyser.GeyserPluginBootstrap;
|
||||
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.configuration.ConfigLoader;
|
||||
import org.geysermc.geyser.configuration.GeyserPluginConfig;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.command.CommandRegistry;
|
||||
import org.geysermc.geyser.command.CommandSourceConverter;
|
||||
import org.geysermc.geyser.command.GeyserCommandSource;
|
||||
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.incendo.cloud.CommandManager;
|
||||
import org.incendo.cloud.execution.ExecutionCoordinator;
|
||||
import org.incendo.cloud.velocity.VelocityCommandManager;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
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.Optional;
|
||||
|
||||
@Plugin(id = "geyser", name = GeyserImpl.NAME + "-Velocity", version = GeyserImpl.VERSION, url = "https://geysermc.org", authors = "GeyserMC")
|
||||
public class GeyserVelocityPlugin implements GeyserPluginBootstrap {
|
||||
|
||||
private final ProxyServer proxyServer;
|
||||
private final CommandManager commandManager;
|
||||
private final PluginContainer container;
|
||||
private final GeyserVelocityLogger geyserLogger;
|
||||
private GeyserCommandManager geyserCommandManager;
|
||||
private GeyserPluginConfig 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
|
||||
@ -110,14 +115,29 @@ public class GeyserVelocityPlugin implements GeyserPluginBootstrap {
|
||||
|
||||
@Override
|
||||
public void onGeyserEnable() {
|
||||
// If e.g. the config failed to load, GeyserImpl was not loaded and we cannot start
|
||||
if (geyser == null) {
|
||||
return;
|
||||
}
|
||||
if (GeyserImpl.getInstance().isReloading()) {
|
||||
if (!loadConfig()) {
|
||||
return;
|
||||
}
|
||||
this.geyserLogger.setDebug(geyserConfig.debugMode());
|
||||
} 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();
|
||||
@ -128,22 +148,10 @@ public class GeyserVelocityPlugin implements GeyserPluginBootstrap {
|
||||
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
|
||||
@ -174,8 +182,8 @@ public class GeyserVelocityPlugin implements GeyserPluginBootstrap {
|
||||
}
|
||||
|
||||
@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().config().notifyOnNewBedrockUpdate()) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -9,12 +9,14 @@ platformRelocate("org.yaml")
|
||||
platformRelocate("it.unimi.dsi.fastutil")
|
||||
platformRelocate("org.cloudburstmc.netty")
|
||||
platformRelocate("org.bstats")
|
||||
platformRelocate("org.incendo")
|
||||
platformRelocate("io.leangen.geantyref") // provided by cloud and Configurate, 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> {
|
||||
|
@ -35,15 +35,17 @@ import net.raphimc.viaproxy.plugins.events.ProxyStopEvent;
|
||||
import net.raphimc.viaproxy.plugins.events.ShouldVerifyOnlineModeEvent;
|
||||
import net.raphimc.viaproxy.protocoltranslator.viaproxy.ViaProxyConfig;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.GeyserLogger;
|
||||
import org.geysermc.geyser.GeyserPluginBootstrap;
|
||||
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.configuration.ConfigLoader;
|
||||
import org.geysermc.geyser.configuration.GeyserPluginConfig;
|
||||
import org.geysermc.geyser.command.CommandRegistry;
|
||||
import org.geysermc.geyser.command.standalone.StandaloneCloudCommandManager;
|
||||
import org.geysermc.geyser.dump.BootstrapDumpInfo;
|
||||
import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough;
|
||||
import org.geysermc.geyser.ping.IGeyserPingPassthrough;
|
||||
@ -51,7 +53,6 @@ import org.geysermc.geyser.platform.viaproxy.listener.GeyserServerTransferListen
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
import org.geysermc.geyser.util.LoopbackUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.spongepowered.configurate.serialize.SerializationException;
|
||||
|
||||
import java.io.File;
|
||||
@ -68,7 +69,8 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserPlugin
|
||||
private final GeyserViaProxyLogger logger = new GeyserViaProxyLogger(LogManager.getLogger("Geyser"));
|
||||
private GeyserPluginConfig config;
|
||||
private GeyserImpl geyser;
|
||||
private GeyserCommandManager commandManager;
|
||||
private StandaloneCloudCommandManager cloud;
|
||||
private CommandRegistry commandRegistry;
|
||||
private IGeyserPingPassthrough pingPassthrough;
|
||||
|
||||
@Override
|
||||
@ -89,7 +91,9 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserPlugin
|
||||
@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);
|
||||
}
|
||||
}
|
||||
@ -130,21 +134,36 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserPlugin
|
||||
|
||||
@Override
|
||||
public void onGeyserEnable() {
|
||||
if (GeyserImpl.getInstance().isReloading()) {
|
||||
// If e.g. the config failed to load, GeyserImpl was not loaded and we cannot start
|
||||
if (geyser == null) {
|
||||
return;
|
||||
}
|
||||
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);
|
||||
}
|
||||
if (this.config.java().authType() == AuthType.FLOODGATE) {
|
||||
ViaProxy.getConfig().setPassthroughBungeecordPlayerInfo(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -168,8 +187,8 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserPlugin
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeyserCommandManager getGeyserCommandManager() {
|
||||
return this.commandManager;
|
||||
public CommandRegistry getCommandRegistry() {
|
||||
return this.commandRegistry;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -187,7 +206,7 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserPlugin
|
||||
return new GeyserViaProxyDumpInfo();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@NonNull
|
||||
@Override
|
||||
public String getServerBindAddress() {
|
||||
if (ViaProxy.getConfig().getBindAddress() instanceof InetSocketAddress socketAddress) {
|
||||
@ -216,6 +235,7 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserPlugin
|
||||
return new File(ROOT_FOLDER, config.floodgateKeyFile()).toPath();
|
||||
}
|
||||
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
private boolean loadConfig() {
|
||||
try {
|
||||
this.config = ConfigLoader.load(new File(ROOT_FOLDER, "config.yml"), GeyserPluginConfig.class, node -> {
|
||||
|
@ -2,4 +2,4 @@ name: "${name}-ViaProxy"
|
||||
version: "${version}"
|
||||
author: "${author}"
|
||||
main: "org.geysermc.geyser.platform.viaproxy.GeyserViaProxyPlugin"
|
||||
min-version: "3.2.1"
|
||||
min-version: "3.3.2"
|
||||
|
@ -0,0 +1,45 @@
|
||||
repositories {
|
||||
// mavenLocal()
|
||||
|
||||
mavenCentral()
|
||||
|
||||
// Floodgate, Cumulus etc.
|
||||
maven("https://repo.opencollab.dev/main")
|
||||
|
||||
// Paper, Velocity
|
||||
maven("https://repo.papermc.io/repository/maven-public")
|
||||
|
||||
// Spigot
|
||||
maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots") {
|
||||
mavenContent { snapshotsOnly() }
|
||||
}
|
||||
|
||||
// BungeeCord
|
||||
maven("https://oss.sonatype.org/content/repositories/snapshots") {
|
||||
mavenContent { snapshotsOnly() }
|
||||
}
|
||||
|
||||
// NeoForge
|
||||
maven("https://maven.neoforged.net/releases") {
|
||||
mavenContent { releasesOnly() }
|
||||
}
|
||||
|
||||
// Minecraft
|
||||
maven("https://libraries.minecraft.net") {
|
||||
name = "minecraft"
|
||||
mavenContent { releasesOnly() }
|
||||
}
|
||||
|
||||
// ViaVersion
|
||||
maven("https://repo.viaversion.com") {
|
||||
name = "viaversion"
|
||||
}
|
||||
|
||||
// Jitpack for e.g. MCPL
|
||||
maven("https://jitpack.io") {
|
||||
content { includeGroupByRegex("com\\.github\\..*") }
|
||||
}
|
||||
|
||||
// For Adventure snapshots
|
||||
maven("https://s01.oss.sonatype.org/content/repositories/snapshots/")
|
||||
}
|
@ -5,6 +5,7 @@ import org.gradle.kotlin.dsl.dependencies
|
||||
import org.gradle.kotlin.dsl.maven
|
||||
|
||||
plugins {
|
||||
id("geyser.build-logic")
|
||||
id("geyser.publish-conventions")
|
||||
id("architectury-plugin")
|
||||
id("dev.architectury.loom")
|
||||
@ -37,6 +38,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
|
||||
}
|
||||
@ -112,12 +117,3 @@ dependencies {
|
||||
minecraft(libs.minecraft)
|
||||
mappings(loom.officialMojangMappings())
|
||||
}
|
||||
|
||||
repositories {
|
||||
// mavenLocal()
|
||||
maven("https://repo.opencollab.dev/main")
|
||||
maven("https://jitpack.io")
|
||||
maven("https://oss.sonatype.org/content/repositories/snapshots/")
|
||||
maven("https://s01.oss.sonatype.org/content/repositories/snapshots/")
|
||||
maven("https://maven.neoforged.net/releases")
|
||||
}
|
@ -11,8 +11,8 @@ modrinth {
|
||||
versionNumber.set(project.version as String + "-" + System.getenv("BUILD_NUMBER"))
|
||||
versionType.set("beta")
|
||||
changelog.set(System.getenv("CHANGELOG") ?: "")
|
||||
gameVersions.add(libs.minecraft.get().version as String)
|
||||
gameVersions.addAll("1.21", libs.minecraft.get().version as String)
|
||||
failSilently.set(true)
|
||||
|
||||
syncBodyFrom.set(rootProject.file("README.md").readText())
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -32,11 +32,10 @@ dependencies {
|
||||
|
||||
api(libs.bundles.protocol)
|
||||
|
||||
api(libs.mcauthlib)
|
||||
api(libs.minecraftauth)
|
||||
api(libs.mcprotocollib) {
|
||||
exclude("io.netty", "netty-all")
|
||||
exclude("com.github.GeyserMC", "packetlib")
|
||||
exclude("com.github.GeyserMC", "mcauthlib")
|
||||
exclude("net.raphimc", "MinecraftAuth")
|
||||
}
|
||||
|
||||
implementation(libs.raknet) {
|
||||
@ -59,6 +58,9 @@ dependencies {
|
||||
// Adventure text serialization
|
||||
api(libs.bundles.adventure)
|
||||
|
||||
// command library
|
||||
api(libs.cloud.core)
|
||||
|
||||
api(libs.erosion.common) {
|
||||
isTransitive = false
|
||||
}
|
||||
@ -99,7 +101,7 @@ sourceSets {
|
||||
blossom {
|
||||
val info = GitInfo()
|
||||
javaSources {
|
||||
property("version", "${info.version} (${info.gitVersion})")
|
||||
property("version", info.version)
|
||||
property("gitVersion", info.gitVersion)
|
||||
property("buildNumber", info.buildNumber.toString())
|
||||
property("branch", info.branch)
|
||||
|
@ -35,11 +35,11 @@ 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";
|
||||
static final String SAVED_AUTH_CHAINS_FILE = "saved-auth-chains.json";
|
||||
|
||||
public static final String GEYSER_CUSTOM_NAMESPACE = "geyser_custom";
|
||||
|
||||
|
@ -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.GeyserConfig;
|
||||
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
|
||||
|
@ -41,6 +41,8 @@ import org.bstats.charts.AdvancedPie;
|
||||
import org.bstats.charts.DrilldownPie;
|
||||
import org.bstats.charts.SimplePie;
|
||||
import org.bstats.charts.SingleLineChart;
|
||||
import net.raphimc.minecraftauth.step.java.session.StepFullJavaSession;
|
||||
import net.raphimc.minecraftauth.step.msa.StepMsaToken;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
@ -62,14 +64,15 @@ 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.configuration.GeyserConfig;
|
||||
import org.geysermc.geyser.command.CommandRegistry;
|
||||
import org.geysermc.geyser.entity.EntityDefinitions;
|
||||
import org.geysermc.geyser.erosion.UnixSocketClientListener;
|
||||
import org.geysermc.geyser.event.GeyserEventBus;
|
||||
@ -95,6 +98,7 @@ import org.geysermc.geyser.translator.text.MessageTranslator;
|
||||
import org.geysermc.geyser.util.AssetUtils;
|
||||
import org.geysermc.geyser.util.DimensionUtils;
|
||||
import org.geysermc.geyser.util.JsonUtils;
|
||||
import org.geysermc.geyser.util.MinecraftAuthLogger;
|
||||
import org.geysermc.geyser.util.NewsHandler;
|
||||
import org.geysermc.geyser.util.VersionCheckUtils;
|
||||
import org.geysermc.geyser.util.WebUtils;
|
||||
@ -123,11 +127,12 @@ import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.function.Consumer;
|
||||
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 Gson GSON = JsonUtils.createGson();
|
||||
|
||||
public static final String NAME = "Geyser";
|
||||
@ -149,12 +154,6 @@ public class GeyserImpl implements GeyserApi {
|
||||
|
||||
private final SessionManager sessionManager = new SessionManager();
|
||||
|
||||
/**
|
||||
* This is used in GeyserConnect to stop the bedrock server binding to a port
|
||||
*/
|
||||
@Setter
|
||||
private static boolean shouldStartListener = true;
|
||||
|
||||
private FloodgateCipher cipher;
|
||||
private FloodgateSkinUploader skinUploader;
|
||||
private NewsHandler newsHandler;
|
||||
@ -177,7 +176,7 @@ public class GeyserImpl implements GeyserApi {
|
||||
|
||||
private PendingMicrosoftAuthentication pendingMicrosoftAuthentication;
|
||||
@Getter(AccessLevel.NONE)
|
||||
private Map<String, String> savedRefreshTokens;
|
||||
private Map<String, String> savedAuthChains;
|
||||
|
||||
@Getter
|
||||
private static GeyserImpl instance;
|
||||
@ -225,9 +224,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("******************************************");
|
||||
@ -260,6 +257,9 @@ public class GeyserImpl implements GeyserApi {
|
||||
CompletableFuture.runAsync(AssetUtils::downloadAndRunClientJarTasks);
|
||||
});
|
||||
|
||||
// Register our general permissions when possible
|
||||
eventBus.subscribe(this, GeyserRegisterPermissionsEvent.class, Permissions::register);
|
||||
|
||||
startInstance();
|
||||
|
||||
GeyserConfig config = bootstrap.config();
|
||||
@ -425,24 +425,27 @@ public class GeyserImpl implements GeyserApi {
|
||||
bedrockThreadCount = Math.max(1, SystemPropertyUtil.getInt("io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
|
||||
}
|
||||
|
||||
if (shouldStartListener) {
|
||||
this.geyserServer = new GeyserServer(this, bedrockThreadCount);
|
||||
this.geyserServer.bind(new InetSocketAddress(config.bedrock().address(), config.bedrock().port()))
|
||||
.whenComplete((avoid, throwable) -> {
|
||||
if (throwable == null) {
|
||||
logger.info(GeyserLocale.getLocaleStringLog("geyser.core.start", config.bedrock().address(),
|
||||
String.valueOf(config.bedrock().port())));
|
||||
this.geyserServer = new GeyserServer(this, bedrockThreadCount);
|
||||
this.geyserServer.bind(new InetSocketAddress(config.bedrock().address(), config.bedrock().port()))
|
||||
.whenComplete((avoid, throwable) -> {
|
||||
String address = config.bedrock().address();
|
||||
String port = String.valueOf(config.bedrock().port()); // otherwise we get commas
|
||||
|
||||
if (throwable == null) {
|
||||
if ("0.0.0.0".equals(address)) {
|
||||
// basically just hide it in the log because some people get confused and try to change it
|
||||
logger.info(GeyserLocale.getLocaleStringLog("geyser.core.start.ip_suppressed", port));
|
||||
} else {
|
||||
String address = config.bedrock().address();
|
||||
int port = config.bedrock().port();
|
||||
logger.severe(GeyserLocale.getLocaleStringLog("geyser.core.fail", address, String.valueOf(port)));
|
||||
if (!"0.0.0.0".equals(address)) {
|
||||
logger.info(Component.text("Suggestion: try setting `address` under `bedrock` in the Geyser config back to 0.0.0.0", NamedTextColor.GREEN));
|
||||
logger.info(Component.text("Then, restart this server.", NamedTextColor.GREEN));
|
||||
}
|
||||
logger.info(GeyserLocale.getLocaleStringLog("geyser.core.start", address, port));
|
||||
}
|
||||
}).join();
|
||||
}
|
||||
} else {
|
||||
logger.severe(GeyserLocale.getLocaleStringLog("geyser.core.fail", address, port));
|
||||
if (!"0.0.0.0".equals(address)) {
|
||||
logger.info(Component.text("Suggestion: try setting `address` under `bedrock` in the Geyser config back to 0.0.0.0", NamedTextColor.GREEN));
|
||||
logger.info(Component.text("Then, restart this server.", NamedTextColor.GREEN));
|
||||
}
|
||||
}
|
||||
}).join();
|
||||
|
||||
if (config.java().authType() == AuthType.FLOODGATE) {
|
||||
try {
|
||||
@ -572,37 +575,82 @@ public class GeyserImpl implements GeyserApi {
|
||||
|
||||
if (config.java().authType() == AuthType.ONLINE) {
|
||||
// May be written/read to on multiple threads from each GeyserSession as well as writing the config
|
||||
savedRefreshTokens = new ConcurrentHashMap<>();
|
||||
savedAuthChains = new ConcurrentHashMap<>();
|
||||
Type type = new TypeToken<Map<String, String>>() { }.getType();
|
||||
|
||||
File tokensFile = bootstrap.getSavedUserLoginsFolder().resolve(Constants.SAVED_REFRESH_TOKEN_FILE).toFile();
|
||||
if (tokensFile.exists()) {
|
||||
Type type = new TypeToken<Map<String, String>>() { }.getType();
|
||||
// TODO Remove after a while - just a migration help
|
||||
//noinspection deprecation
|
||||
File refreshTokensFile = bootstrap.getSavedUserLoginsFolder().resolve(Constants.SAVED_REFRESH_TOKEN_FILE).toFile();
|
||||
if (refreshTokensFile.exists()) {
|
||||
logger.info("Migrating refresh tokens to auth chains...");
|
||||
Map<String, String> refreshTokens = null;
|
||||
try (FileReader reader = new FileReader(refreshTokensFile)) {
|
||||
refreshTokens = GSON.fromJson(reader, type);
|
||||
} catch (IOException e) {
|
||||
// ignored - we'll just delete this file :))
|
||||
}
|
||||
|
||||
Map<String, String> refreshTokenFile = null;
|
||||
try (FileReader reader = new FileReader(tokensFile)) {
|
||||
refreshTokenFile = GSON.fromJson(reader, type);
|
||||
if (refreshTokens != null) {
|
||||
List<String> validUsers = config.savedUserLogins();
|
||||
final Gson gson = new Gson();
|
||||
for (Map.Entry<String, String> entry : refreshTokens.entrySet()) {
|
||||
String user = entry.getKey();
|
||||
if (!validUsers.contains(user)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Migrate refresh tokens to auth chains
|
||||
try {
|
||||
StepFullJavaSession javaSession = PendingMicrosoftAuthentication.AUTH_FLOW.apply(false, 10);
|
||||
StepFullJavaSession.FullJavaSession fullJavaSession = javaSession.getFromInput(
|
||||
MinecraftAuthLogger.INSTANCE,
|
||||
PendingMicrosoftAuthentication.AUTH_CLIENT,
|
||||
new StepMsaToken.RefreshToken(entry.getValue())
|
||||
);
|
||||
|
||||
String authChain = gson.toJson(javaSession.toJson(fullJavaSession));
|
||||
savedAuthChains.put(user, authChain);
|
||||
} catch (Exception e) {
|
||||
GeyserImpl.getInstance().getLogger().warning("Could not migrate " + entry.getKey() + " to an auth chain! " +
|
||||
"They will need to sign in the next time they join Geyser.");
|
||||
}
|
||||
|
||||
// Ensure the new additions are written to the file
|
||||
scheduleAuthChainsWrite();
|
||||
}
|
||||
}
|
||||
|
||||
// Finally: Delete it. Goodbye!
|
||||
refreshTokensFile.delete();
|
||||
}
|
||||
|
||||
File authChainsFile = bootstrap.getSavedUserLoginsFolder().resolve(Constants.SAVED_AUTH_CHAINS_FILE).toFile();
|
||||
if (authChainsFile.exists()) {
|
||||
Map<String, String> authChainFile = null;
|
||||
try (FileReader reader = new FileReader(authChainsFile)) {
|
||||
authChainFile = GSON.fromJson(reader, type);
|
||||
} catch (IOException e) {
|
||||
logger.error("Cannot load saved user tokens!", e);
|
||||
}
|
||||
if (refreshTokenFile != null) {
|
||||
if (authChainFile != null) {
|
||||
List<String> validUsers = config.savedUserLogins();
|
||||
boolean doWrite = false;
|
||||
for (Map.Entry<String, String> entry : refreshTokenFile.entrySet()) {
|
||||
for (Map.Entry<String, String> entry : authChainFile.entrySet()) {
|
||||
String user = entry.getKey();
|
||||
if (!validUsers.contains(user)) {
|
||||
// Perform a write to this file to purge the now-unused name
|
||||
doWrite = true;
|
||||
continue;
|
||||
}
|
||||
savedRefreshTokens.put(user, entry.getValue());
|
||||
savedAuthChains.put(user, entry.getValue());
|
||||
}
|
||||
if (doWrite) {
|
||||
scheduleRefreshTokensWrite();
|
||||
scheduleAuthChainsWrite();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
savedRefreshTokens = null;
|
||||
savedAuthChains = null;
|
||||
}
|
||||
|
||||
newsHandler.handleNews(null, NewsItemAction.ON_SERVER_STARTED);
|
||||
@ -683,16 +731,11 @@ public class GeyserImpl implements GeyserApi {
|
||||
bootstrap.getGeyserLogger().info(GeyserLocale.getLocaleStringLog("geyser.core.shutdown.kick.done"));
|
||||
}
|
||||
|
||||
scheduledThread.shutdown();
|
||||
geyserServer.shutdown();
|
||||
if (skinUploader != null) {
|
||||
skinUploader.close();
|
||||
}
|
||||
newsHandler.shutdown();
|
||||
|
||||
if (this.erosionUnixListener != null) {
|
||||
this.erosionUnixListener.close();
|
||||
}
|
||||
runIfNonNull(scheduledThread, ScheduledExecutorService::shutdown);
|
||||
runIfNonNull(geyserServer, GeyserServer::shutdown);
|
||||
runIfNonNull(skinUploader, FloodgateSkinUploader::close);
|
||||
runIfNonNull(newsHandler, NewsHandler::shutdown);
|
||||
runIfNonNull(erosionUnixListener, UnixSocketClientListener::close);
|
||||
|
||||
Registries.RESOURCE_PACKS.get().clear();
|
||||
|
||||
@ -704,7 +747,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));
|
||||
@ -732,7 +774,7 @@ public class GeyserImpl implements GeyserApi {
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
public boolean isProductionEnvironment() {
|
||||
// First is if Blossom runs, second is if Blossom doesn't run
|
||||
//noinspection ConstantConditions,MismatchedStringCase - changes in production
|
||||
//noinspection ConstantConditions - changes in production
|
||||
return !("git-local/dev-0000000".equals(GeyserImpl.GIT_VERSION) || "${gitVersion}".equals(GeyserImpl.GIT_VERSION));
|
||||
}
|
||||
|
||||
@ -742,9 +784,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
|
||||
@ -854,11 +899,11 @@ public class GeyserImpl implements GeyserApi {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String refreshTokenFor(@NonNull String bedrockName) {
|
||||
return savedRefreshTokens.get(bedrockName);
|
||||
public String authChainFor(@NonNull String bedrockName) {
|
||||
return savedAuthChains.get(bedrockName);
|
||||
}
|
||||
|
||||
public void saveRefreshToken(@NonNull String bedrockName, @NonNull String refreshToken) {
|
||||
public void saveAuthChain(@NonNull String bedrockName, @NonNull String authChain) {
|
||||
if (!config().savedUserLogins().contains(bedrockName)) {
|
||||
// Do not save this login
|
||||
return;
|
||||
@ -866,18 +911,24 @@ public class GeyserImpl implements GeyserApi {
|
||||
|
||||
// We can safely overwrite old instances because MsaAuthenticationService#getLoginResponseFromRefreshToken
|
||||
// refreshes the token for us
|
||||
if (!Objects.equals(refreshToken, savedRefreshTokens.put(bedrockName, refreshToken))) {
|
||||
scheduleRefreshTokensWrite();
|
||||
if (!Objects.equals(authChain, savedAuthChains.put(bedrockName, authChain))) {
|
||||
scheduleAuthChainsWrite();
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleRefreshTokensWrite() {
|
||||
private <T> void runIfNonNull(T nullable, Consumer<T> consumer) {
|
||||
if (nullable != null) {
|
||||
consumer.accept(nullable);
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleAuthChainsWrite() {
|
||||
scheduledThread.execute(() -> {
|
||||
// Ensure all writes are handled on the same thread
|
||||
File savedTokens = getBootstrap().getSavedUserLoginsFolder().resolve(Constants.SAVED_REFRESH_TOKEN_FILE).toFile();
|
||||
File savedAuthChains = getBootstrap().getSavedUserLoginsFolder().resolve(Constants.SAVED_AUTH_CHAINS_FILE).toFile();
|
||||
Type type = new TypeToken<Map<String, String>>() { }.getType();
|
||||
try (FileWriter writer = new FileWriter(savedTokens)) {
|
||||
GSON.toJson(savedRefreshTokens, type, writer);
|
||||
try (FileWriter writer = new FileWriter(savedAuthChains)) {
|
||||
GSON.toJson(this.savedAuthChains, type, writer);
|
||||
} catch (IOException e) {
|
||||
getLogger().error("Unable to write saved refresh tokens!", e);
|
||||
}
|
||||
|
@ -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 {
|
||||
|
||||
@ -132,6 +133,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
Normale Datei
63
core/src/main/java/org/geysermc/geyser/Permissions.java
Normale Datei
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
302
core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java
Normale Datei
302
core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java
Normale Datei
@ -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()));
|
||||
}
|
||||
}
|
129
core/src/main/java/org/geysermc/geyser/command/ExceptionHandlers.java
Normale Datei
129
core/src/main/java/org/geysermc/geyser/command/ExceptionHandlers.java
Normale Datei
@ -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;
|
||||
}
|
||||
}
|
||||
|
136
core/src/main/java/org/geysermc/geyser/command/GeyserPermission.java
Normale Datei
136
core/src/main/java/org/geysermc/geyser/command/GeyserPermission.java
Normale Datei
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -27,90 +27,82 @@ package org.geysermc.geyser.command.defaults;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
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.GeyserConfig;
|
||||
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.config().bedrock().broadcastPort();
|
||||
}
|
||||
String ip = fullAddress[0];
|
||||
final String ip = ipArgument.replace("<", "").replace(">", "");
|
||||
final int port = portArgument != null ? portArgument : geyser.config().bedrock().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;
|
||||
}
|
||||
|
||||
@ -119,37 +111,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.bedrock().broadcastPort() == config.bedrock().port()) {
|
||||
if (port != config.bedrock().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.bedrock().port() + ")");
|
||||
sender.sendMessage("Re-run the command with the port in the config, or change the `bedrock` `port` in the config.");
|
||||
if (config.asPluginConfig().map(pluginConfig -> pluginConfig.bedrock().cloneRemotePort()).orElse(false)) {
|
||||
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("Re-run the command with the port in the config, or change the `bedrock` `port` in the config.");
|
||||
if (config.asPluginConfig().map(plugin -> plugin.bedrock().cloneRemotePort()).orElse(false)) {
|
||||
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.bedrock().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.bedrock().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.bedrock().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.bedrock().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.bedrock().enableProxyProtocol()) {
|
||||
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.");
|
||||
}
|
||||
|
||||
@ -158,14 +150,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");
|
||||
}
|
||||
|
||||
@ -179,7 +171,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...");
|
||||
JsonObject output;
|
||||
try {
|
||||
String hostname = URLEncoder.encode(ip, StandardCharsets.UTF_8);
|
||||
@ -201,31 +193,31 @@ public class ConnectionTestCommand extends GeyserCommand {
|
||||
JsonObject pong = ping.getAsJsonObject("pong");
|
||||
String remoteMotd = pong.get("motd").getAsString();
|
||||
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").getAsBoolean()) {
|
||||
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!");
|
||||
JsonElement message = output.get("message");
|
||||
if (message != null && !message.getAsString().isEmpty()) {
|
||||
sender.sendMessage("Got the error message: " + message.getAsString());
|
||||
source.sendMessage("Got the error message: " + message.getAsString());
|
||||
}
|
||||
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);
|
||||
}
|
||||
});
|
||||
@ -236,9 +228,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,42 +29,69 @@ import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
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.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 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;
|
||||
}
|
||||
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;
|
||||
@ -75,6 +102,7 @@ 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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -83,12 +111,13 @@ public class DumpCommand extends GeyserCommand {
|
||||
|
||||
Gson gson = new GsonBuilder().setPrettyPrinting().create();
|
||||
|
||||
sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.collecting", sender.locale()));
|
||||
source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.collecting", source.locale()));
|
||||
String dumpData;
|
||||
try {
|
||||
dumpData = gson.toJson(new DumpInfo(addLog));
|
||||
} catch (JsonSyntaxException e) {
|
||||
sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.collect_error", sender.locale()));
|
||||
DumpInfo dump = new DumpInfo(geyser, addLog);
|
||||
dumpData = gson.toJson(dump);
|
||||
} catch (Exception e) {
|
||||
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;
|
||||
}
|
||||
@ -96,21 +125,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;
|
||||
JsonObject responseNode;
|
||||
@ -118,33 +147,22 @@ public class DumpCommand extends GeyserCommand {
|
||||
response = WebUtils.post(DUMP_URL + "documents", dumpData);
|
||||
responseNode = (JsonObject) new JsonParser().parse(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").getAsString() : response));
|
||||
source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.upload_error_short", source.locale()) + ": " + (responseNode.has("message") ? responseNode.get("message").getAsString() : response));
|
||||
return;
|
||||
}
|
||||
|
||||
uploadedDumpUrl = DUMP_URL + responseNode.get("key").getAsString();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
@ -23,25 +23,27 @@
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.translator.sound.block;
|
||||
package org.geysermc.geyser.command.defaults;
|
||||
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.protocol.bedrock.data.LevelEvent;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket;
|
||||
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.translator.sound.BlockSoundInteractionTranslator;
|
||||
import org.geysermc.geyser.translator.sound.SoundTranslator;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
import org.incendo.cloud.context.CommandContext;
|
||||
|
||||
@SoundTranslator(blocks = {"door", "fence_gate"})
|
||||
public class DoorSoundInteractionTranslator implements BlockSoundInteractionTranslator {
|
||||
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 translate(GeyserSession session, Vector3f position, String identifier) {
|
||||
if (identifier.contains("iron")) return;
|
||||
LevelEventPacket levelEventPacket = new LevelEventPacket();
|
||||
levelEventPacket.setType(LevelEvent.SOUND_DOOR_OPEN);
|
||||
levelEventPacket.setPosition(position);
|
||||
levelEventPacket.setData(0);
|
||||
session.sendUpstreamPacket(levelEventPacket);
|
||||
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.google.gson.JsonObject;
|
||||
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();
|
||||
JsonObject response = WebUtils.getJson("https://download.geysermc.org/v2/projects/geyser/versions/latest/builds/latest");
|
||||
int latestBuildNumber = response.get("build").getAsInt();
|
||||
|
||||
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,41 @@
|
||||
/*
|
||||
* 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.JsonProperty;
|
||||
import lombok.Getter;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
@Getter
|
||||
@ConfigSerializable
|
||||
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);
|
||||
}
|
||||
}
|
@ -82,7 +82,7 @@ public class DumpInfo {
|
||||
private final FlagsInfo flagsInfo;
|
||||
private final List<ExtensionInfo> extensionInfo;
|
||||
|
||||
public DumpInfo(boolean addLog) {
|
||||
public DumpInfo(GeyserImpl geyser, boolean addLog) {
|
||||
this.versionInfo = new VersionInfo();
|
||||
|
||||
this.cpuCount = Runtime.getRuntime().availableProcessors();
|
||||
@ -92,7 +92,7 @@ public class DumpInfo {
|
||||
|
||||
this.gitInfo = new GitInfo(GeyserImpl.BUILD_NUMBER, GeyserImpl.COMMIT.substring(0, 7), GeyserImpl.COMMIT, GeyserImpl.BRANCH, GeyserImpl.REPOSITORY);
|
||||
|
||||
this.config = GeyserImpl.getInstance().config();
|
||||
this.config = geyser.config();
|
||||
|
||||
String md5Hash = "unknown";
|
||||
String sha256Hash = "unknown";
|
||||
@ -107,7 +107,7 @@ public class DumpInfo {
|
||||
//noinspection UnstableApiUsage
|
||||
sha256Hash = byteSource.hash(Hashing.sha256()).toString();
|
||||
} catch (Exception e) {
|
||||
if (GeyserImpl.getInstance().config().debugMode()) {
|
||||
if (this.config.debugMode()) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
@ -116,18 +116,22 @@ public class DumpInfo {
|
||||
this.ramInfo = new RamInfo();
|
||||
|
||||
if (addLog) {
|
||||
this.logsInfo = new LogsInfo();
|
||||
this.logsInfo = new LogsInfo(geyser);
|
||||
}
|
||||
|
||||
this.userPlatforms = new Object2IntOpenHashMap<>();
|
||||
for (GeyserSession session : GeyserImpl.getInstance().getSessionManager().getAllSessions()) {
|
||||
for (GeyserSession session : geyser.getSessionManager().getAllSessions()) {
|
||||
DeviceOs device = session.getClientData().getDeviceOs();
|
||||
userPlatforms.put(device, userPlatforms.getOrDefault(device, 0) + 1);
|
||||
}
|
||||
|
||||
this.connectionAttempts = GeyserImpl.getInstance().getGeyserServer().getConnectionAttempts();
|
||||
if (geyser.getGeyserServer() != null) {
|
||||
this.connectionAttempts = geyser.getGeyserServer().getConnectionAttempts();
|
||||
} else {
|
||||
this.connectionAttempts = 0; // Fallback if Geyser failed to fully startup
|
||||
}
|
||||
|
||||
this.bootstrapInfo = GeyserImpl.getInstance().getBootstrap().getDumpInfo();
|
||||
this.bootstrapInfo = geyser.getBootstrap().getDumpInfo();
|
||||
|
||||
this.flagsInfo = new FlagsInfo();
|
||||
|
||||
@ -233,10 +237,10 @@ public class DumpInfo {
|
||||
public static class LogsInfo {
|
||||
private String link;
|
||||
|
||||
public LogsInfo() {
|
||||
public LogsInfo(GeyserImpl geyser) {
|
||||
try {
|
||||
Map<String, String> fields = new HashMap<>();
|
||||
fields.put("content", FileUtils.readAllLines(GeyserImpl.getInstance().getBootstrap().getLogsPath()).collect(Collectors.joining("\n")));
|
||||
fields.put("content", FileUtils.readAllLines(geyser.getBootstrap().getLogsPath()).collect(Collectors.joining("\n")));
|
||||
|
||||
JsonObject logData = new JsonParser().parse(WebUtils.postForm("https://api.mclo.gs/1/log", fields)).getAsJsonObject();
|
||||
|
||||
|
Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden Mehr anzeigen
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren