Fabric improvements

Mainly in commands - the old permissions file no longer needs to exist.
This commit is contained in:
Camotoy 2022-10-24 13:21:02 -04:00
parent a612be60aa
commit 254f0da03c
No known key found for this signature in database
GPG key ID: 7EEFB66FE798081F
11 changed files with 81 additions and 158 deletions

View file

@ -19,6 +19,9 @@ dependencies {
// Fabric API. This is technically optional, but you probably want it anyway.
modImplementation(libs.fabric.api)
// This should be in the libs TOML, but something about modImplementation AND include just doesn't work
include(modImplementation("me.lucko", "fabric-permissions-api", "0.2-SNAPSHOT"))
// PSA: Some older mods, compiled on Loom 0.2.1, might have outdated Maven POMs.
// You may need to force-disable transitiveness on them.

View file

@ -25,12 +25,14 @@
package org.geysermc.geyser.platform.fabric;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.geysermc.geyser.GeyserLogger;
import org.geysermc.geyser.text.ChatColor;
public class GeyserFabricLogger implements GeyserLogger {
private final Logger logger = LogManager.getLogger("geyser-fabric");
private boolean debug;
@ -69,6 +71,16 @@ public class GeyserFabricLogger implements GeyserLogger {
logger.info(message);
}
@Override
public void sendMessage(Component message) {
// As of Java Edition 1.19.2, Fabric's console doesn't natively support legacy format
String flattened = LegacyComponentSerializer.legacySection().serialize(message);
// Add the reset at the end, or else format will persist... forever.
// https://cdn.discordapp.com/attachments/573909525132738590/1033904509170225242/unknown.png
String text = ChatColor.toANSI(flattened) + ChatColor.ANSI_RESET;
info(text);
}
@Override
public void debug(String message) {
if (debug) {

View file

@ -29,6 +29,7 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer;
import net.minecraft.commands.CommandSourceStack;
@ -55,29 +56,21 @@ import org.geysermc.geyser.util.FileUtils;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.util.*;
public class GeyserFabricMod implements ModInitializer, GeyserBootstrap {
private static GeyserFabricMod instance;
private boolean reloading;
private GeyserImpl connector;
private GeyserImpl geyser;
private ModContainer mod;
private Path dataFolder;
private MinecraftServer server;
/**
* Commands that don't require any permission level to ran
*/
private List<String> playerCommands;
private final List<GeyserFabricCommandExecutor> commandExecutors = new ArrayList<>();
private GeyserCommandManager geyserCommandManager;
private GeyserFabricConfiguration geyserConfig;
private GeyserFabricLogger geyserLogger;
@ -111,8 +104,6 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap {
File configFile = FileUtils.fileOrCopiedFromResource(dataFolder.resolve("config.yml").toFile(), "config.yml",
(x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this);
this.geyserConfig = FileUtils.loadConfig(configFile, GeyserFabricConfiguration.class);
File permissionsFile = fileOrCopiedFromResource(dataFolder.resolve("permissions.yml").toFile(), "permissions.yml");
this.playerCommands = Arrays.asList(FileUtils.loadConfig(permissionsFile, GeyserFabricPermissions.class).getCommands());
} catch (IOException ex) {
LogManager.getLogger("geyser-fabric").error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
ex.printStackTrace();
@ -123,10 +114,14 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap {
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
this.geyser = GeyserImpl.load(PlatformType.FABRIC, this);
if (server == null) {
// Server has yet to start
// Register onDisable so players are properly kicked
ServerLifecycleEvents.SERVER_STOPPING.register((server) -> onDisable());
ServerPlayConnectionEvents.JOIN.register((handler, $, $$) -> GeyserFabricUpdateListener.onPlayReady(handler));
} else {
// Server has started and this is a reload
startGeyser(this.server);
@ -170,38 +165,37 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap {
geyserConfig.loadFloodgate(this, floodgate.orElse(null));
this.connector = GeyserImpl.load(PlatformType.FABRIC, this);
GeyserImpl.start(); // shrug
GeyserImpl.start();
this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(connector);
this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser);
this.geyserCommandManager = new GeyserCommandManager(connector);
this.geyserCommandManager = new GeyserCommandManager(geyser);
this.geyserCommandManager.init();
this.geyserWorldManager = new GeyserFabricWorldManager(server);
// Start command building
// Set just "geyser" as the help command
GeyserFabricCommandExecutor helpExecutor = new GeyserFabricCommandExecutor(connector,
(GeyserCommand) connector.commandManager().getCommands().get("help"), !playerCommands.contains("help"));
commandExecutors.add(helpExecutor);
GeyserFabricCommandExecutor helpExecutor = new GeyserFabricCommandExecutor(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 : connector.commandManager().getCommands().entrySet()) {
GeyserFabricCommandExecutor executor = new GeyserFabricCommandExecutor(connector, (GeyserCommand) command.getValue(),
!playerCommands.contains(command.getKey()));
commandExecutors.add(executor);
builder.then(Commands.literal(command.getKey()).executes(executor));
for (Map.Entry<String, Command> command : geyser.commandManager().getCommands().entrySet()) {
GeyserFabricCommandExecutor executor = new GeyserFabricCommandExecutor(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));
}
server.getCommands().getDispatcher().register(builder);
}
@Override
public void onDisable() {
if (connector != null) {
connector.shutdown();
connector = null;
if (geyser != null) {
geyser.shutdown();
geyser = null;
}
if (!reloading) {
this.server = null;
@ -267,34 +261,6 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap {
this.reloading = reloading;
}
private File fileOrCopiedFromResource(File file, String name) throws IOException {
if (!file.exists()) {
//noinspection ResultOfMethodCallIgnored
file.createNewFile();
FileOutputStream fos = new FileOutputStream(file);
InputStream input = getResource(name);
byte[] bytes = new byte[input.available()];
//noinspection ResultOfMethodCallIgnored
input.read(bytes);
for(char c : new String(bytes).toCharArray()) {
fos.write(c);
}
fos.flush();
input.close();
fos.close();
}
return file;
}
public List<GeyserFabricCommandExecutor> getCommandExecutors() {
return commandExecutors;
}
public static GeyserFabricMod getInstance() {
return instance;
}

View file

@ -25,26 +25,19 @@
package org.geysermc.geyser.platform.fabric;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import me.lucko.fabric.api.permissions.v0.Permissions;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import org.geysermc.geyser.Constants;
import org.geysermc.geyser.platform.fabric.command.FabricCommandSender;
import org.geysermc.geyser.util.VersionCheckUtils;
/**
* A class outline of the permissions.yml file
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public class GeyserFabricPermissions {
public final class GeyserFabricUpdateListener {
public static void onPlayReady(ServerGamePacketListenerImpl handler) {
if (Permissions.check(handler.player, Constants.UPDATE_PERMISSION, 2)) {
VersionCheckUtils.checkForGeyserUpdate(() -> new FabricCommandSender(handler.player.createCommandSourceStack()));
}
}
/**
* The minimum permission level a command source must have in order for it to run commands that are restricted
*/
@JsonIgnore
public static final int RESTRICTED_MIN_LEVEL = 2;
@JsonProperty("commands")
private String[] commands;
public String[] getCommands() {
return this.commands;
private GeyserFabricUpdateListener() {
}
}

View file

@ -25,12 +25,13 @@
package org.geysermc.geyser.platform.fabric.command;
import me.lucko.fabric.api.permissions.v0.Permissions;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.platform.fabric.GeyserFabricMod;
import org.geysermc.geyser.text.ChatColor;
import javax.annotation.Nonnull;
@ -57,22 +58,23 @@ public class FabricCommandSender implements GeyserCommandSource {
}
}
@Override
public void sendMessage(net.kyori.adventure.text.Component message) {
if (source.getEntity() instanceof ServerPlayer player) {
String decoded = GsonComponentSerializer.gson().serialize(message);
player.displayClientMessage(Component.Serializer.fromJson(decoded), false);
return;
}
GeyserCommandSource.super.sendMessage(message);
}
@Override
public boolean isConsole() {
return !(source.getEntity() instanceof ServerPlayer);
}
@Override
public boolean hasPermission(String s) {
// Mostly copied from fabric's world manager since the method there takes a GeyserSession
// Workaround for our commands because fabric doesn't have native permissions
for (GeyserFabricCommandExecutor executor : GeyserFabricMod.getInstance().getCommandExecutors()) {
if (executor.getCommand().permission().equals(s)) {
return executor.canRun(source);
}
}
return false;
public boolean hasPermission(String permission) {
return Permissions.check(source, permission);
}
}

View file

@ -27,12 +27,12 @@ package org.geysermc.geyser.platform.fabric.command;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.context.CommandContext;
import me.lucko.fabric.api.permissions.v0.Permissions;
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.fabric.GeyserFabricMod;
import org.geysermc.geyser.platform.fabric.GeyserFabricPermissions;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.ChatColor;
import org.geysermc.geyser.text.GeyserLocale;
@ -40,27 +40,15 @@ import org.geysermc.geyser.text.GeyserLocale;
import java.util.Collections;
public class GeyserFabricCommandExecutor extends GeyserCommandExecutor implements Command<CommandSourceStack> {
private final GeyserCommand command;
/**
* Whether the command requires an OP permission level of 2 or greater
*/
private final boolean requiresPermission;
public GeyserFabricCommandExecutor(GeyserImpl connector, GeyserCommand command, boolean requiresPermission) {
public GeyserFabricCommandExecutor(GeyserImpl connector, GeyserCommand command) {
super(connector, Collections.singletonMap(command.name(), command));
this.command = command;
this.requiresPermission = requiresPermission;
}
/**
* Determine whether or not a command source is allowed to run a given executor.
*
* @param source The command source attempting to run the command
* @return True if the command source is allowed to
*/
public boolean canRun(CommandSourceStack source) {
return !requiresPermission() || source.hasPermission(GeyserFabricPermissions.RESTRICTED_MIN_LEVEL);
public boolean testPermission(CommandSourceStack source) {
return Permissions.check(source, command.permission(), command.isSuggestedOpOnly() ? 2 : 0);
}
@Override
@ -68,8 +56,8 @@ public class GeyserFabricCommandExecutor extends GeyserCommandExecutor implement
CommandSourceStack source = (CommandSourceStack) context.getSource();
FabricCommandSender sender = new FabricCommandSender(source);
GeyserSession session = getGeyserSession(sender);
if (!canRun(source)) {
sender.sendMessage(GeyserLocale.getLocaleStringLog("geyser.bootstrap.command.permission_fail"));
if (!testPermission(source)) {
sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.locale()));
return 0;
}
if (this.command.name().equals("reload")) {
@ -83,15 +71,4 @@ public class GeyserFabricCommandExecutor extends GeyserCommandExecutor implement
command.execute(session, sender, new String[0]);
return 0;
}
public GeyserCommand getCommand() {
return command;
}
/**
* Returns whether the command requires permission level of {@link GeyserFabricPermissions#RESTRICTED_MIN_LEVEL} or higher to be ran
*/
public boolean requiresPermission() {
return requiresPermission;
}
}

View file

@ -29,6 +29,7 @@ import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.nbt.NbtMap;
import com.nukkitx.nbt.NbtMapBuilder;
import com.nukkitx.nbt.NbtType;
import me.lucko.fabric.api.permissions.v0.Permissions;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.ListTag;
import net.minecraft.server.MinecraftServer;
@ -39,8 +40,6 @@ import net.minecraft.world.item.WrittenBookItem;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.LecternBlockEntity;
import org.geysermc.geyser.level.GeyserWorldManager;
import org.geysermc.geyser.platform.fabric.GeyserFabricMod;
import org.geysermc.geyser.platform.fabric.command.GeyserFabricCommandExecutor;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.inventory.LecternInventoryTranslator;
import org.geysermc.geyser.util.BlockEntityUtils;
@ -124,14 +123,8 @@ public class GeyserFabricWorldManager extends GeyserWorldManager {
@Override
public boolean hasPermission(GeyserSession session, String permission) {
// Workaround for our commands because fabric doesn't have native permissions
for (GeyserFabricCommandExecutor executor : GeyserFabricMod.getInstance().getCommandExecutors()) {
if (executor.getCommand().permission().equals(permission)) {
return executor.canRun(getPlayer(session).createCommandSourceStack());
}
}
return false;
ServerPlayer player = getPlayer(session);
return Permissions.check(player, permission);
}
private ServerPlayer getPlayer(GeyserSession session) {

View file

@ -25,6 +25,7 @@
"depends": {
"fabricloader": ">=0.14.8",
"fabric": "*",
"minecraft": ">=1.19"
"minecraft": ">=1.19",
"fabric-permissions-api-v0": "*"
}
}

View file

@ -1,13 +0,0 @@
# Uncomment any commands that you wish to be run by clients
# Commented commands require an OP permission of 2 or greater
commands:
- help
- advancements
- statistics
- settings
- offhand
- tooltips
# - list
# - reload
# - version
# - dump

View file

@ -26,6 +26,7 @@
package org.geysermc.geyser.text;
public class ChatColor {
public static final String ANSI_RESET = (char) 0x1b + "[0m";
public static final char ESCAPE = '§';
public static final String BLACK = ESCAPE + "0";
@ -64,7 +65,7 @@ public class ChatColor {
string = string.replace(ITALIC, (char) 0x1b + "[3m");
string = string.replace(UNDERLINE, (char) 0x1b + "[4m");
string = string.replace(STRIKETHROUGH, (char) 0x1b + "[9m");
string = string.replace(RESET, (char) 0x1b + "[0m");
string = string.replace(RESET, ANSI_RESET);
string = string.replace(BLACK, (char) 0x1b + "[0;30m");
string = string.replace(DARK_BLUE, (char) 0x1b + "[0;34m");
string = string.replace(DARK_GREEN, (char) 0x1b + "[0;32m");
@ -83,19 +84,4 @@ public class ChatColor {
string = string.replace(WHITE, (char) 0x1b + "[37;1m");
return string;
}
public String translateAlternateColorCodes(char color, String message) {
return message.replace(color, ESCAPE);
}
/**
* Remove all colour formatting tags from a message
*
* @param message Message to remove colour tags from
*
* @return The sanitised message
*/
public static String stripColors(String message) {
return message = message.replaceAll("(&([a-fk-or0-9]))","").replaceAll("(§([a-fk-or0-9]))","").replaceAll("s/\\x1b\\[[0-9;]*[a-zA-Z]//g","");
}
}

View file

@ -26,6 +26,9 @@ commodore = "2.2"
bungeecord = "a7c6ede"
velocity = "3.0.0"
sponge = "8.0.0"
fabric-minecraft = "1.19.1"
fabric-loader = "0.14.8"
fabric-api = "0.58.5+1.19.1"
[libraries]
jackson-annotations = { group = "com.fasterxml.jackson.core", name = "jackson-annotations", version.ref = "jackson" }
@ -63,9 +66,9 @@ paper-api = { group = "io.papermc.paper", name = "paper-api", version.ref = "pap
paper-mojangapi = { group = "io.papermc.paper", name = "paper-mojangapi", version.ref = "paper" }
# check these on https://modmuss50.me/fabric.html
fabric-minecraft = { group = "com.mojang", name = "minecraft", version = "1.19.1" }
fabric-loader = { group = "net.fabricmc", name = "fabric-loader", version = "0.14.8" }
fabric-api = { group = "net.fabricmc.fabric-api", name = "fabric-api", version = "0.58.5+1.19.1" }
fabric-minecraft = { group = "com.mojang", name = "minecraft", version.ref = "fabric-minecraft" }
fabric-loader = { group = "net.fabricmc", name = "fabric-loader", version.ref = "fabric-loader" }
fabric-api = { group = "net.fabricmc.fabric-api", name = "fabric-api", version.ref = "fabric-api" }
adapters-spigot = { group = "org.geysermc.geyser.adapters", name = "spigot-all", version.ref = "adapters" }
bungeecord-proxy = { group = "com.github.SpigotMC.BungeeCord", name = "bungeecord-proxy", version.ref = "bungeecord" }