feat(config-commands): add client-side command for modifying your own configs
Some checks failed
ci/woodpecker/push/docs Pipeline was successful
ci/woodpecker/push/jfmod Pipeline failed

This commit is contained in:
Johannes Frohnmeyer 2024-07-31 17:24:04 +02:00
parent e5e8522f8b
commit 2c52a5a63b
Signed by: Johannes
GPG Key ID: E76429612C2929F4
7 changed files with 188 additions and 62 deletions

View File

@ -0,0 +1,37 @@
package io.gitlab.jfronny.libjf.config.impl.commands.client;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import io.gitlab.jfronny.libjf.config.impl.commands.CommandEnv;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
public record ClientCommandEnv(CommandDispatcher<FabricClientCommandSource> dispatcher) implements CommandEnv<FabricClientCommandSource> {
@Override
public LiteralArgumentBuilder<FabricClientCommandSource> literal(String name) {
return ClientCommandManager.literal(name);
}
@Override
public <T> RequiredArgumentBuilder<FabricClientCommandSource, T> argument(String name, ArgumentType<T> type) {
return ClientCommandManager.argument(name, type);
}
@Override
public boolean isOperator(FabricClientCommandSource source) {
return true;
}
@Override
public void sendFeedback(CommandContext<FabricClientCommandSource> context, net.minecraft.text.Text text, boolean broadcast) {
context.getSource().sendFeedback(text);
}
@Override
public void sendError(CommandContext<FabricClientCommandSource> context, net.minecraft.text.Text text) {
context.getSource().sendError(text);
}
}

View File

@ -0,0 +1,13 @@
package io.gitlab.jfronny.libjf.config.impl.commands.client;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.config.impl.commands.JfConfigCommand;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
public class ClientConfigCommand implements ClientModInitializer {
@Override
public void onInitializeClient() {
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> JfConfigCommand.register(new ClientCommandEnv(dispatcher), LibJf.MOD_ID + "c"));
}
}

View File

@ -0,0 +1,20 @@
package io.gitlab.jfronny.libjf.config.impl.commands;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import net.minecraft.command.CommandSource;
import net.minecraft.text.Text;
public interface CommandEnv<S extends CommandSource> {
LiteralArgumentBuilder<S> literal(String name);
<T> RequiredArgumentBuilder<S, T> argument(String name, ArgumentType<T> type);
boolean isOperator(S source);
CommandDispatcher<S> dispatcher();
void sendFeedback(CommandContext<S> context, Text text, boolean broadcast);
void sendError(CommandContext<S> context, Text text);
}

View File

@ -7,13 +7,10 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.exceptions.DynamicCommandExceptionType;
import io.gitlab.jfronny.commons.throwable.ThrowingRunnable;
import io.gitlab.jfronny.commons.throwable.ThrowingSupplier;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.config.api.v2.*;
import io.gitlab.jfronny.libjf.config.api.v2.type.Type;
import io.gitlab.jfronny.libjf.config.impl.ConfigCore;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.command.CommandSource;
import net.minecraft.text.MutableText;
import net.minecraft.text.Text;
import net.minecraft.util.Language;
@ -21,81 +18,82 @@ import net.minecraft.util.Language;
import java.util.function.Consumer;
import java.util.function.Function;
import static net.minecraft.server.command.CommandManager.argument;
import static net.minecraft.server.command.CommandManager.literal;
public class JfConfigCommand implements ModInitializer {
public class JfConfigCommand {
private static final String MOD_ID = "libjf-config-commands";
private MutableText text(String text) {
private static MutableText text(String text) {
return Text.literal("[" + MOD_ID + "] " + text);
}
@Override
public void onInitialize() {
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> {
LiteralArgumentBuilder<ServerCommandSource> c_libjf = literal(LibJf.MOD_ID);
LiteralArgumentBuilder<ServerCommandSource> c_config = literal("config")
.requires((serverCommandSource) -> serverCommandSource.hasPermissionLevel(4))
.executes(context -> {
MutableText text = text("Loaded configs for:");
ConfigHolder.getInstance().getRegistered().forEach((s, config) -> text.append("\n- " + s));
context.getSource().sendFeedback(() -> text, false);
return Command.SINGLE_SUCCESS;
});
LiteralArgumentBuilder<ServerCommandSource> c_reload = literal("reload").executes(context -> {
ConfigHolder.getInstance().getRegistered().forEach((mod, config) -> config.load());
context.getSource().sendFeedback(() -> text("Reloaded configs"), true);
return Command.SINGLE_SUCCESS;
});
LiteralArgumentBuilder<ServerCommandSource> c_reset = literal("reset").executes(context -> {
context.getSource().sendError(text("Please specify a config to reset"));
return Command.SINGLE_SUCCESS;
});
ConfigHolder.getInstance().getRegistered().forEach((id, config) -> {
c_reload.then(literal(id).executes(context -> {
config.load();
context.getSource().sendFeedback(() -> text("Reloaded config for " + id), true);
public static <S extends CommandSource> void register(CommandEnv<S> env, String name) {
LiteralArgumentBuilder<S> c_root = env.literal(name);
LiteralArgumentBuilder<S> c_config = env.literal("config")
.requires(env::isOperator)
.executes(context -> {
MutableText text = text("Loaded configs for:");
ConfigHolder.getInstance().getRegistered().forEach((s, config) -> text.append("\n- " + s));
env.sendFeedback(context, text, false);
return Command.SINGLE_SUCCESS;
}));
registerEntries(config, id, c_config, c_reset, cns -> {
LiteralArgumentBuilder<ServerCommandSource> c_instance = literal(id);
cns.accept(c_instance);
return c_instance;
});
});
dispatcher.register(c_libjf.then(c_config.then(c_reload).then(c_reset)));
LiteralArgumentBuilder<S> c_reload = env.literal("reload").executes(context -> {
ConfigHolder.getInstance().getRegistered().forEach((mod, config) -> config.load());
env.sendFeedback(context, text("Reloaded configs"), true);
return Command.SINGLE_SUCCESS;
});
LiteralArgumentBuilder<S> c_reset = env.literal("reset").executes(context -> {
env.sendError(context, text("Please specify a config to reset"));
return Command.SINGLE_SUCCESS;
});
ConfigHolder.getInstance().getRegistered().forEach((id, config) -> {
c_reload.then(env.literal(id).executes(context -> {
config.load();
env.sendFeedback(context, text("Reloaded config for " + id), true);
return Command.SINGLE_SUCCESS;
}));
registerEntries(env, config, id, c_config, c_reset, cns -> {
LiteralArgumentBuilder<S> c_instance = env.literal(id);
cns.accept(c_instance);
return c_instance;
});
});
env.dispatcher().register(c_root.then(c_config.then(c_reload).then(c_reset)));
}
private void registerEntries(ConfigCategory config, String subpath, LiteralArgumentBuilder<ServerCommandSource> c_config, LiteralArgumentBuilder<ServerCommandSource> c_reset, Function<Consumer<LiteralArgumentBuilder<ServerCommandSource>>, LiteralArgumentBuilder<ServerCommandSource>> pathGen) {
private static <S extends CommandSource> void registerEntries(
CommandEnv<S> env,
ConfigCategory config,
String subpath,
LiteralArgumentBuilder<S> c_config,
LiteralArgumentBuilder<S> c_reset,
Function<Consumer<LiteralArgumentBuilder<S>>, LiteralArgumentBuilder<S>> pathGen
) {
c_config.then(pathGen.apply(cns -> {
cns.executes(context -> {
context.getSource().sendFeedback(() -> text(subpath + " is a category"), false);
env.sendFeedback(context, text(subpath + " is a category"), false);
return Command.SINGLE_SUCCESS;
});
for (EntryInfo<?> entry : config.getEntries()) {
registerEntry(config, subpath, cns, entry);
registerEntry(env, config, subpath, cns, entry);
}
}));
c_reset.then(pathGen.apply(cns -> {
cns.executes(context -> {
config.reset();
context.getSource().sendFeedback(() -> text("Reset config for " + subpath), true);
env.sendFeedback(context, text("Reset config for " + subpath), true);
return Command.SINGLE_SUCCESS;
});
config.getPresets().forEach((id2, preset) -> {
cns.then(literal(id2).executes(context -> {
cns.then(env.literal(id2).executes(context -> {
preset.run();
context.getSource().sendFeedback(() -> text("Loaded preset " + id2 + " for " + subpath), true);
env.sendFeedback(context, text("Loaded preset " + id2 + " for " + subpath), true);
return Command.SINGLE_SUCCESS;
}));
});
}));
config.getCategories().forEach((id2, cfg) -> {
registerEntries(cfg, cfg.getCategoryPath(), c_config, c_reset, cns -> {
registerEntries(env, cfg, cfg.getCategoryPath(), c_config, c_reset, cns -> {
return pathGen.apply(cns1 -> {
LiteralArgumentBuilder<ServerCommandSource> c_instance2 = literal(id2);
LiteralArgumentBuilder<S> c_instance2 = env.literal(id2);
cns.accept(c_instance2);
cns1.then(c_instance2);
});
@ -103,41 +101,47 @@ public class JfConfigCommand implements ModInitializer {
});
}
private final DynamicCommandExceptionType eType = new DynamicCommandExceptionType(o -> {
private static final DynamicCommandExceptionType eType = new DynamicCommandExceptionType(o -> {
if (o instanceof Throwable throwable) {
return Text.literal("Could not execute command: " + throwable.getMessage());
} else return Text.literal("Could not execute command");
});
private <T> void registerEntry(ConfigCategory config, String subpath, LiteralArgumentBuilder<ServerCommandSource> cns, EntryInfo<T> entry) {
private static <T, S extends CommandSource> void registerEntry(
CommandEnv<S> env,
ConfigCategory config,
String subpath,
LiteralArgumentBuilder<S> cns,
EntryInfo<T> entry
) {
if (!entry.supportsRepresentation()) return;
LiteralArgumentBuilder<ServerCommandSource> c_entry = literal(entry.getName()).executes(context -> {
LiteralArgumentBuilder<S> c_entry = env.literal(entry.getName()).executes(context -> {
String msg = "The value of " + subpath + "." + entry.getName() + " is ";
Text visualized = visualizeOption(config, entry, tryRun(entry::getValue));
context.getSource().sendFeedback(() -> text(msg).append(visualized), false);
env.sendFeedback(context, text(msg).append(visualized), false);
return Command.SINGLE_SUCCESS;
});
ArgumentType<?> type = getType(entry);
if (type != null) {
c_entry.then(argument("value", type).executes(context -> {
c_entry.then(env.argument("value", type).executes(context -> {
@SuppressWarnings("unchecked") T value = context.getArgument("value", (Class<T>) entry.getValueType().asClass());
tryRun(() -> {
entry.setValue(value);
config.getRoot().write();
});
context.getSource().sendFeedback(() -> text("Set " + subpath + "." + entry.getName() + " to ")
env.sendFeedback(context, text("Set " + subpath + "." + entry.getName() + " to ")
.append(visualizeOption(config, entry, value)), true);
return Command.SINGLE_SUCCESS;
}));
}
else if (entry.getValueType().isEnum()) {
for (T enumConstant : ((Type.TEnum<T>)entry.getValueType()).options()) {
c_entry.then(literal(enumConstant.toString()).executes(context -> {
c_entry.then(env.literal(enumConstant.toString()).executes(context -> {
tryRun(() -> {
entry.setValue(enumConstant);
config.getRoot().write();
});
context.getSource().sendFeedback(() -> text("Set " + subpath + "." + entry.getName() + " to " + enumConstant)
env.sendFeedback(context, text("Set " + subpath + "." + entry.getName() + " to " + enumConstant)
.append(visualizeOption(config, entry, enumConstant)), true);
return Command.SINGLE_SUCCESS;
}));
@ -146,7 +150,7 @@ public class JfConfigCommand implements ModInitializer {
cns.then(c_entry);
}
private <T> Text visualizeOption(ConfigCategory config, EntryInfo<T> entry, T value) {
private static <T> Text visualizeOption(ConfigCategory config, EntryInfo<T> entry, T value) {
Language lang = Language.getInstance();
String key = null;
Type type = entry.getValueType();
@ -167,7 +171,7 @@ public class JfConfigCommand implements ModInitializer {
else return Text.translatableWithFallback(key, String.valueOf(value));
}
private <T> ArgumentType<?> getType(EntryInfo<T> info) {
private static <T> ArgumentType<?> getType(EntryInfo<T> info) {
Type type = info.getValueType();
if (type.isInt()) return IntegerArgumentType.integer((int) info.getMinValue(), (int) info.getMaxValue());
else if (type.isLong()) return LongArgumentType.longArg((long) info.getMinValue(), (long) info.getMaxValue());
@ -178,11 +182,11 @@ public class JfConfigCommand implements ModInitializer {
else return null;
}
private <T> T tryRun(ThrowingSupplier<T, ?> supplier) throws CommandSyntaxException {
private static <T> T tryRun(ThrowingSupplier<T, ?> supplier) throws CommandSyntaxException {
return supplier.orThrow(eType::create).get();
}
private void tryRun(ThrowingRunnable<?> supplier) throws CommandSyntaxException {
private static void tryRun(ThrowingRunnable<?> supplier) throws CommandSyntaxException {
supplier.orThrow(eType::create).run();
}
}

View File

@ -0,0 +1,38 @@
package io.gitlab.jfronny.libjf.config.impl.commands.server;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import io.gitlab.jfronny.libjf.config.impl.commands.CommandEnv;
import net.minecraft.server.command.CommandManager;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.text.Text;
public record ServerCommandEnv(CommandDispatcher<ServerCommandSource> dispatcher) implements CommandEnv<ServerCommandSource> {
@Override
public LiteralArgumentBuilder<ServerCommandSource> literal(String name) {
return CommandManager.literal(name);
}
@Override
public <T> RequiredArgumentBuilder<ServerCommandSource, T> argument(String name, ArgumentType<T> type) {
return CommandManager.argument(name, type);
}
@Override
public boolean isOperator(ServerCommandSource source) {
return source.hasPermissionLevel(4);
}
@Override
public void sendFeedback(CommandContext<ServerCommandSource> context, Text text, boolean broadcast) {
context.getSource().sendFeedback(() -> text, broadcast);
}
@Override
public void sendError(CommandContext<ServerCommandSource> context, Text text) {
context.getSource().sendError(text);
}
}

View File

@ -0,0 +1,13 @@
package io.gitlab.jfronny.libjf.config.impl.commands.server;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.config.impl.commands.JfConfigCommand;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
public class ServerConfigCommand implements ModInitializer {
@Override
public void onInitialize() {
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> JfConfigCommand.register(new ServerCommandEnv(dispatcher), LibJf.MOD_ID));
}
}

View File

@ -13,7 +13,8 @@
"license": "MIT",
"environment": "*",
"entrypoints": {
"main": ["io.gitlab.jfronny.libjf.config.impl.commands.JfConfigCommand"]
"main": ["io.gitlab.jfronny.libjf.config.impl.commands.server.ServerConfigCommand"],
"client": ["io.gitlab.jfronny.libjf.config.impl.commands.client.ClientConfigCommand"]
},
"depends": {
"fabricloader": ">=0.12.0",