Skip to content

Commit

Permalink
Rework command flags. Fixes #164 and closes #217.
Browse files Browse the repository at this point in the history
  • Loading branch information
Earthcomputer committed Oct 5, 2023
1 parent eae83ae commit 670f2b8
Show file tree
Hide file tree
Showing 13 changed files with 180 additions and 61 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package net.earthcomputer.clientcommands.command;

import org.jetbrains.annotations.Nullable;

public final class Argument<T> {
private final String name;
private final T defaultValue;

private Argument(String name, @Nullable T defaultValue) {
this.name = name;
this.defaultValue = defaultValue;
}

public static <T> Argument<@Nullable T> of(String name) {
return new Argument<>(name, null);
}

public static <T> Argument<T> ofDefaulted(String name, T defaultValue) {
return new Argument<>(name, defaultValue);
}

public static Argument<Boolean> ofFlag(String name) {
return new Argument<>(name, false);
}

public String getName() {
return name;
}

public String getFlag() {
return "--" + name;
}

public static boolean isFlag(String argument) {
return argument.startsWith("--");
}

public T getDefaultValue() {
return defaultValue;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@

public class CEnchantCommand {

private static final int FLAG_SIMULATE = 1;
private static final Argument<Boolean> FLAG_SIMULATE = Argument.ofFlag("simulate");

public static void register(CommandDispatcher<FabricClientCommandSource> dispatcher) {
var cenchant = dispatcher.register(literal("cenchant"));
dispatcher.register(literal("cenchant")
.then(literal("--simulate")
.redirect(cenchant, ctx -> withFlags(ctx.getSource(), FLAG_SIMULATE, true)))
.then(literal(FLAG_SIMULATE.getFlag())
.redirect(cenchant, ctx -> withArg(ctx.getSource(), FLAG_SIMULATE, true)))
.then(argument("itemAndEnchantmentsPredicate", itemAndEnchantmentsPredicate().withEnchantmentPredicate(CEnchantCommand::enchantmentPredicate).constrainMaxLevel())
.executes(ctx -> cenchant(ctx.getSource(), getItemAndEnchantmentsPredicate(ctx, "itemAndEnchantmentsPredicate")))));
}
Expand All @@ -55,7 +55,7 @@ private static int cenchant(FabricClientCommandSource source, ItemAndEnchantment
return Command.SINGLE_SUCCESS;
}

boolean simulate = getFlag(source, FLAG_SIMULATE);
boolean simulate = getArg(source, FLAG_SIMULATE);

var result = EnchantmentCracker.manipulateEnchantments(
itemAndEnchantmentsPredicate.item(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,19 @@ public class CalcCommand {

private static final SimpleCommandExceptionType TOO_DEEPLY_NESTED_EXCEPTION = new SimpleCommandExceptionType(Text.translatable("commands.ccalc.tooDeeplyNested"));

private static final int FLAG_PARSE = 1;
private static final Argument<Boolean> ARG_PARSE = Argument.ofFlag("parse");

public static void register(CommandDispatcher<FabricClientCommandSource> dispatcher) {
var ccalc = dispatcher.register(literal("ccalc"));
dispatcher.register(literal("ccalc")
.then(literal("--parse")
.redirect(ccalc, ctx -> withFlags(ctx.getSource(), FLAG_PARSE, true)))
.then(literal(ARG_PARSE.getFlag())
.redirect(ccalc, ctx -> withArg(ctx.getSource(), ARG_PARSE, true)))
.then(argument("expr", expression())
.executes(ctx -> evaluateExpression(ctx.getSource(), getExpression(ctx, "expr")))));
}

private static int evaluateExpression(FabricClientCommandSource source, ExpressionArgumentType.Expression expression) throws CommandSyntaxException {
if (getFlag(source, FLAG_PARSE)) {
if (getArg(source, ARG_PARSE)) {
Text parsedTree;
try {
parsedTree = expression.getParsedTree(0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import com.demonwav.mcdev.annotations.Translatable;
import com.mojang.brigadier.context.CommandContext;
import net.earthcomputer.clientcommands.interfaces.IFlaggedCommandSource;
import net.earthcomputer.clientcommands.interfaces.IClientCommandSource;
import net.earthcomputer.clientcommands.mixin.InGameHudAccessor;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import net.minecraft.client.MinecraftClient;
Expand All @@ -13,22 +13,16 @@

public class ClientCommandHelper {

public static boolean getFlag(CommandContext<FabricClientCommandSource> ctx, int flag) {
return getFlag(ctx.getSource(), flag);
public static <T> T getArg(CommandContext<FabricClientCommandSource> ctx, Argument<T> arg) {
return getArg(ctx.getSource(), arg);
}

public static boolean getFlag(FabricClientCommandSource source, int flag) {
return (((IFlaggedCommandSource) source).getFlags() & flag) != 0;
public static <T> T getArg(FabricClientCommandSource source, Argument<T> arg) {
return ((IClientCommandSource) source).clientcommands_getArg(arg);
}

public static FabricClientCommandSource withFlags(FabricClientCommandSource source, int flags, boolean value) {
IFlaggedCommandSource flaggedSource = (IFlaggedCommandSource) source;

if (value) {
return (FabricClientCommandSource) flaggedSource.withFlags(flaggedSource.getFlags() | flags);
} else {
return (FabricClientCommandSource) flaggedSource.withFlags(flaggedSource.getFlags() & ~flags);
}
public static <T> FabricClientCommandSource withArg(FabricClientCommandSource source, Argument<T> arg, T value) {
return (FabricClientCommandSource) ((IClientCommandSource) source).clientcommands_withArg(arg, value);
}

public static void sendError(Text error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,21 @@

public class FindCommand {

private static final int FLAG_KEEP_SEARCHING = 1;
private static final Argument<Boolean> ARG_KEEP_SEARCHING = Argument.ofFlag("keep-searching");

private static final SimpleCommandExceptionType NO_MATCH_EXCEPTION = new SimpleCommandExceptionType(Text.translatable("commands.cfind.noMatch"));

public static void register(CommandDispatcher<FabricClientCommandSource> dispatcher) {
var cfind = dispatcher.register(literal("cfind"));
dispatcher.register(literal("cfind")
.then(literal("--keep-searching")
.redirect(cfind, ctx -> withFlags(ctx.getSource(), FLAG_KEEP_SEARCHING, true)))
.then(literal(ARG_KEEP_SEARCHING.getFlag())
.redirect(cfind, ctx -> withArg(ctx.getSource(), ARG_KEEP_SEARCHING, true)))
.then(argument("filter", entities())
.executes(ctx -> listEntities(ctx.getSource(), ctx.getArgument("filter", CEntitySelector.class)))));
}

private static int listEntities(FabricClientCommandSource source, CEntitySelector selector) throws CommandSyntaxException {
boolean keepSearching = getFlag(source, FLAG_KEEP_SEARCHING);
boolean keepSearching = getArg(source, ARG_KEEP_SEARCHING);
if (keepSearching) {
String taskName = TaskManager.addTask("cfind", new FindTask(source, selector));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,22 +62,22 @@
import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.*;

public class FindItemCommand {
private static final int FLAG_NO_SEARCH_SHULKER_BOX = 1;
private static final int FLAG_KEEP_SEARCHING = 2;
private static final Argument<Boolean> FLAG_NO_SEARCH_SHULKER_BOX = Argument.ofFlag("no-search-shulker-box");
private static final Argument<Boolean> FLAG_KEEP_SEARCHING = Argument.ofFlag("keep-searching");

@SuppressWarnings("unchecked")
public static void register(CommandDispatcher<FabricClientCommandSource> dispatcher, CommandRegistryAccess registryAccess) {
var cfinditem = dispatcher.register(literal("cfinditem"));
dispatcher.register(literal("cfinditem")
.then(literal("--no-search-shulker-box")
.redirect(cfinditem, ctx -> withFlags(ctx.getSource(), FLAG_NO_SEARCH_SHULKER_BOX, true)))
.then(literal("--keep-searching")
.redirect(cfinditem, ctx -> withFlags(ctx.getSource(), FLAG_KEEP_SEARCHING, true)))
.then(literal(FLAG_NO_SEARCH_SHULKER_BOX.getFlag())
.redirect(cfinditem, ctx -> withArg(ctx.getSource(), FLAG_NO_SEARCH_SHULKER_BOX, true)))
.then(literal(FLAG_KEEP_SEARCHING.getFlag())
.redirect(cfinditem, ctx -> withArg(ctx.getSource(), FLAG_KEEP_SEARCHING, true)))
.then(argument("item", withString(itemPredicate(registryAccess)))
.executes(ctx ->
findItem(ctx,
getFlag(ctx, FLAG_NO_SEARCH_SHULKER_BOX),
getFlag(ctx, FLAG_KEEP_SEARCHING),
getArg(ctx, FLAG_NO_SEARCH_SHULKER_BOX),
getArg(ctx, FLAG_KEEP_SEARCHING),
getWithString(ctx, "item", (Class<Predicate<ItemStack>>) (Class<?>) Predicate.class)))));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,16 @@
public class GlowCommand {
private static final SimpleCommandExceptionType FAILED_EXCEPTION = new SimpleCommandExceptionType(Text.translatable("commands.cglow.entity.failed"));

private static final int FLAG_KEEP_SEARCHING = 1;
private static final Argument<Boolean> FLAG_KEEP_SEARCHING = Argument.ofFlag("keep-searching");

public static void register(CommandDispatcher<FabricClientCommandSource> dispatcher) {
var cglow = dispatcher.register(literal("cglow"));
dispatcher.register(literal("cglow")
.then(literal("--keep-searching-entities")
.redirect(cglow, ctx -> withFlags(ctx.getSource(), FLAG_KEEP_SEARCHING, true)))
.then(literal(FLAG_KEEP_SEARCHING.getFlag())
.redirect(cglow, ctx -> withArg(ctx.getSource(), FLAG_KEEP_SEARCHING, true)))
.then(literal("entities")
.then(argument("targets", entities())
.executes(ctx -> glowEntities(ctx.getSource(), ctx.getArgument("targets", CEntitySelector.class), getFlag(ctx, FLAG_KEEP_SEARCHING) ? 0 : 30, 0xffffff))
.executes(ctx -> glowEntities(ctx.getSource(), ctx.getArgument("targets", CEntitySelector.class), getArg(ctx, FLAG_KEEP_SEARCHING) ? 0 : 30, 0xffffff))
.then(argument("seconds", integer(0))
.executes(ctx -> glowEntities(ctx.getSource(), ctx.getArgument("targets", CEntitySelector.class), getInteger(ctx, "seconds"), 0xffffff))
.then(literal("color")
Expand Down Expand Up @@ -76,7 +76,7 @@ public static void register(CommandDispatcher<FabricClientCommandSource> dispatc
}

private static int glowEntities(FabricClientCommandSource source, CEntitySelector entitySelector, int seconds, int color) throws CommandSyntaxException {
boolean keepSearching = getFlag(source, FLAG_KEEP_SEARCHING);
boolean keepSearching = getArg(source, FLAG_KEEP_SEARCHING);
if (keepSearching) {
String taskName = TaskManager.addTask("cglow", new SimpleTask() {
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@

public class TooltipCommand {

private static final int FLAG_ADVANCED = 1;
private static final Argument<Boolean> FLAG_ADVANCED = Argument.ofFlag("advanced");

public static void register(CommandDispatcher<FabricClientCommandSource> dispatcher, CommandRegistryAccess registryAccess) {
var ctooltip = dispatcher.register(literal("ctooltip"));
dispatcher.register(literal("ctooltip")
.then(literal("--advanced")
.redirect(ctooltip, ctx -> withFlags(ctx.getSource(), FLAG_ADVANCED, true)))
.then(literal(FLAG_ADVANCED.getFlag())
.redirect(ctooltip, ctx -> withArg(ctx.getSource(), FLAG_ADVANCED, true)))
.then(literal("held")
.executes(ctx -> showTooltip(ctx.getSource(), ctx.getSource().getPlayer().getMainHandStack(), "held")))
.then(literal("stack")
Expand All @@ -32,7 +32,7 @@ public static void register(CommandDispatcher<FabricClientCommandSource> dispatc
private static int showTooltip(FabricClientCommandSource source, ItemStack stack, String type) {
source.sendFeedback(Text.translatable("commands.ctooltip.header." + type));

TooltipContext context = getFlag(source, FLAG_ADVANCED) ? TooltipContext.ADVANCED : TooltipContext.BASIC;
TooltipContext context = getArg(source, FLAG_ADVANCED) ? TooltipContext.ADVANCED : TooltipContext.BASIC;

List<Text> tooltip = stack.getTooltip(source.getPlayer(), context);
for (Text line : tooltip) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package net.earthcomputer.clientcommands.interfaces;

import com.mojang.brigadier.context.ParsedCommandNode;
import com.mojang.brigadier.suggestion.Suggestion;
import net.earthcomputer.clientcommands.command.Argument;
import net.minecraft.command.CommandSource;
import org.jetbrains.annotations.Nullable;

import java.util.List;

public interface IClientCommandSource {
<T> T clientcommands_getArg(Argument<T> arg);

<T> IClientCommandSource clientcommands_withArg(Argument<T> arg, T value);

@Nullable
List<Suggestion> clientcommands_filterSuggestions(List<Suggestion> suggestions, List<ParsedCommandNode<CommandSource>> parsedNodes);
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package net.earthcomputer.clientcommands.mixin;

import com.mojang.brigadier.ParseResults;
import com.mojang.brigadier.suggestion.Suggestion;
import com.mojang.brigadier.suggestion.Suggestions;
import net.earthcomputer.clientcommands.interfaces.IClientCommandSource;
import net.minecraft.client.gui.screen.ChatInputSuggestor;
import net.minecraft.command.CommandSource;
import org.jetbrains.annotations.Nullable;
import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Slice;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

import java.util.List;
import java.util.concurrent.CompletableFuture;

@Mixin(ChatInputSuggestor.class)
public class MixinChatInputSuggestor {
@Shadow private @Nullable ParseResults<CommandSource> parse;
@Shadow private @Nullable CompletableFuture<Suggestions> pendingSuggestions;

@Inject(
method = "refresh",
slice = @Slice(from = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayNetworkHandler;getCommandDispatcher()Lcom/mojang/brigadier/CommandDispatcher;")),
at = @At(value = "FIELD", target = "Lnet/minecraft/client/gui/screen/ChatInputSuggestor;pendingSuggestions:Ljava/util/concurrent/CompletableFuture;", opcode = Opcodes.PUTFIELD, shift = At.Shift.AFTER, ordinal = 0)
)
private void filterSuggestions(CallbackInfo ci) {
assert this.pendingSuggestions != null && this.parse != null;
this.pendingSuggestions = this.pendingSuggestions.thenApply(suggestions -> {
CommandSource source = this.parse.getContext().getSource();
if (source instanceof IClientCommandSource mySource) {
List<Suggestion> newSuggestions = mySource.clientcommands_filterSuggestions(suggestions.getList(), this.parse.getContext().getNodes());
return newSuggestions == null ? suggestions : new Suggestions(suggestions.getRange(), newSuggestions);
} else {
return suggestions;
}
});
}
}
Original file line number Diff line number Diff line change
@@ -1,40 +1,71 @@
package net.earthcomputer.clientcommands.mixin;

import net.earthcomputer.clientcommands.interfaces.IFlaggedCommandSource;
import com.google.common.collect.ImmutableMap;
import com.mojang.brigadier.context.ParsedCommandNode;
import com.mojang.brigadier.suggestion.Suggestion;
import com.mojang.brigadier.tree.LiteralCommandNode;
import net.earthcomputer.clientcommands.command.Argument;
import net.earthcomputer.clientcommands.interfaces.IClientCommandSource;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientCommandSource;
import net.minecraft.client.network.ClientPlayNetworkHandler;

import net.minecraft.command.CommandSource;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;

@Mixin(ClientCommandSource.class)
public class MixinClientCommandSource implements IFlaggedCommandSource {
import java.util.HashSet;
import java.util.List;
import java.util.Set;

@Mixin(ClientCommandSource.class)
public class MixinClientCommandSource implements IClientCommandSource {
@Shadow
@Final
private ClientPlayNetworkHandler networkHandler;

@Shadow
@Final
private MinecraftClient client;

@Unique
private int flags;
private ImmutableMap<Argument<?>, Object> arguments = ImmutableMap.of();

@SuppressWarnings("unchecked")
@Override
public int getFlags() {
return this.flags;
public <T> T clientcommands_getArg(Argument<T> arg) {
return (T) this.arguments.getOrDefault(arg, arg.getDefaultValue());
}

@Override
public IFlaggedCommandSource withFlags(int flags) {
public <T> IClientCommandSource clientcommands_withArg(Argument<T> arg, T value) {
MixinClientCommandSource source = (MixinClientCommandSource) (Object) new ClientCommandSource(this.networkHandler, this.client);
source.flags = flags;

source.arguments = ImmutableMap.<Argument<?>, Object>builderWithExpectedSize(this.arguments.size() + 1).putAll(this.arguments).put(arg, value).build();
return source;
}

@Override
@Nullable
public List<Suggestion> clientcommands_filterSuggestions(List<Suggestion> suggestions, List<ParsedCommandNode<CommandSource>> parsedNodes) {
Set<String> seenFlags = new HashSet<>();
for (Argument<?> arg : arguments.keySet()) {
seenFlags.add(arg.getFlag());
}
for (ParsedCommandNode<CommandSource> parsedNode : parsedNodes) {
if (parsedNode.getNode() instanceof LiteralCommandNode<CommandSource> literal) {
String flag = literal.getLiteral();
if (Argument.isFlag(flag)) {
seenFlags.add(flag);
}
}
}

if (seenFlags.isEmpty()) {
return null;
} else {
return suggestions.stream().filter(suggestion -> !seenFlags.contains(suggestion.getText())).toList();
}
}
}
Loading

0 comments on commit 670f2b8

Please sign in to comment.