Config support

can prob be improved but that will be done later :trollface:
This commit is contained in:
chipmunk 2023-03-11 21:56:16 -05:00
parent 2023ae3cd1
commit 88dc82eb1e
17 changed files with 738 additions and 664 deletions

View file

@ -1,21 +1,64 @@
package land.chipmunk.chipmunkmod;
import net.fabricmc.api.ModInitializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ChipmunkMod implements ModInitializer {
// This logger is used to write text to the console and the log file.
// It is considered best practice to use your mod id as the logger's name.
// That way, it's clear which mod wrote info, warnings, and errors.
public static final Logger LOGGER = LoggerFactory.getLogger("chipmunkmod");
@Override
public void onInitialize () {
// This code runs as soon as Minecraft is in a mod-load-ready state.
// However, some things (like resources) may still be uninitialized.
// Proceed with mild caution.
LOGGER.info("Hello Fabric world!");
}
}
package land.chipmunk.chipmunkmod;
import net.fabricmc.api.ModInitializer;
import java.io.InputStream;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
public class ChipmunkMod implements ModInitializer {
// This logger is used to write text to the console and the log file.
// It is considered best practice to use your mod id as the logger's name.
// That way, it's clear which mod wrote info, warnings, and errors.
public static final Logger LOGGER = LoggerFactory.getLogger("chipmunkmod");
public static Configuration CONFIG;
@Override
public void onInitialize () {
// This code runs as soon as Minecraft is in a mod-load-ready state.
// However, some things (like resources) may still be uninitialized.
// Proceed with mild caution.
try {
new File("config").mkdirs(); // TODO: Clean this up
CONFIG = loadConfig();
} catch (IOException exception) {
throw new RuntimeException("Could not load the config", exception);
}
LOGGER.info("Hello Fabric world!");
}
public static Configuration loadConfig () throws IOException {
final Gson gson = new Gson();
final File file = new File("config/chipmunkmod.json");
if (!file.exists()) {
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("default_config.json");
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
final StringBuilder sb = new StringBuilder();
while (reader.ready()) sb.append((char) reader.read());
final String defaultConfig = sb.toString();
// Write the default config
BufferedWriter configWriter = new BufferedWriter(new FileWriter(file));
configWriter.write(defaultConfig);
configWriter.close();
return gson.fromJson(defaultConfig, Configuration.class);
}
InputStream is = new FileInputStream(file);
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
return gson.fromJson(reader, Configuration.class);
}
}

View file

@ -0,0 +1,17 @@
package land.chipmunk.chipmunkmod;
import land.chipmunk.chipmunkmod.data.BlockArea;
import net.minecraft.util.math.BlockPos;
public class Configuration {
public CommandManager commands = new CommandManager();
public CommandCore core = new CommandCore();
public class CommandManager {
public String prefix = ".";
}
public class CommandCore {
public BlockArea relativeArea = new BlockArea(new BlockPos(0, 0, 0), new BlockPos(15, 0, 15));
}
}

View file

@ -1,73 +1,74 @@
package land.chipmunk.chipmunkmod.command;
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.exceptions.CommandSyntaxException;
import net.minecraft.command.CommandException;
import net.minecraft.text.ClickEvent;
import net.minecraft.text.Text;
import net.minecraft.text.Texts;
import net.minecraft.text.MutableText;
import net.minecraft.util.Formatting;
import net.minecraft.client.MinecraftClient;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import land.chipmunk.chipmunkmod.commands.*;
public class CommandManager {
public static CommandDispatcher<FabricClientCommandSource> dispatcher = new CommandDispatcher();
public static String prefix = ".";
public static void executeCommand (String command) {
final MinecraftClient client = MinecraftClient.getInstance();
final FabricClientCommandSource commandSource = (FabricClientCommandSource) client.getNetworkHandler().getCommandSource();
try {
dispatcher.execute(command, commandSource);
} catch (CommandSyntaxException e) {
commandSource.sendError(Texts.toText(e.getRawMessage()));
final Text context = getContext(e);
if (context != null) commandSource.sendError(context);
} catch (CommandException e) {
commandSource.sendError(e.getTextMessage());
} catch (RuntimeException e) {
commandSource.sendError(Text.of(e.getMessage()));
}
}
public static Text getContext (CommandSyntaxException exception) {
final int _cursor = exception.getCursor();
final String input = exception.getInput();
if (input == null || _cursor < 0) {
return null;
}
final MutableText text = Text.literal("")
.formatted(Formatting.GRAY);
text.setStyle(text.getStyle().withClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, prefix + input)));
final int cursor = Math.min(input.length(), _cursor);
if (cursor > CommandSyntaxException.CONTEXT_AMOUNT) {
text.append(Text.literal("..."));
}
text
.append(Text.literal(input.substring(Math.max(0, cursor - CommandSyntaxException.CONTEXT_AMOUNT), cursor)))
.append(Text.literal(input.substring(cursor)).formatted(Formatting.RED, Formatting.UNDERLINE))
.append(Text.translatable("command.context.here").formatted(Formatting.RED, Formatting.ITALIC));
return text;
}
public static LiteralArgumentBuilder<FabricClientCommandSource> literal (String name) { return LiteralArgumentBuilder.<FabricClientCommandSource>literal(name); }
public static <T> RequiredArgumentBuilder<FabricClientCommandSource, T> argument (String name, ArgumentType<T> type) { return RequiredArgumentBuilder.<FabricClientCommandSource, T>argument(name, type); }
static {
TestCommand.register(dispatcher);
CoreCommand.register(dispatcher);
UsernameCommand.register(dispatcher);
}
package land.chipmunk.chipmunkmod.command;
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.exceptions.CommandSyntaxException;
import net.minecraft.command.CommandException;
import net.minecraft.text.ClickEvent;
import net.minecraft.text.Text;
import net.minecraft.text.Texts;
import net.minecraft.text.MutableText;
import net.minecraft.util.Formatting;
import net.minecraft.client.MinecraftClient;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import land.chipmunk.chipmunkmod.ChipmunkMod;
import land.chipmunk.chipmunkmod.commands.*;
public class CommandManager {
public static CommandDispatcher<FabricClientCommandSource> dispatcher = new CommandDispatcher();
public static String prefix = ChipmunkMod.CONFIG.commands.prefix;
public static void executeCommand (String command) {
final MinecraftClient client = MinecraftClient.getInstance();
final FabricClientCommandSource commandSource = (FabricClientCommandSource) client.getNetworkHandler().getCommandSource();
try {
dispatcher.execute(command, commandSource);
} catch (CommandSyntaxException e) {
commandSource.sendError(Texts.toText(e.getRawMessage()));
final Text context = getContext(e);
if (context != null) commandSource.sendError(context);
} catch (CommandException e) {
commandSource.sendError(e.getTextMessage());
} catch (RuntimeException e) {
commandSource.sendError(Text.of(e.getMessage()));
}
}
public static Text getContext (CommandSyntaxException exception) {
final int _cursor = exception.getCursor();
final String input = exception.getInput();
if (input == null || _cursor < 0) {
return null;
}
final MutableText text = Text.literal("")
.formatted(Formatting.GRAY);
text.setStyle(text.getStyle().withClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, prefix + input)));
final int cursor = Math.min(input.length(), _cursor);
if (cursor > CommandSyntaxException.CONTEXT_AMOUNT) {
text.append(Text.literal("..."));
}
text
.append(Text.literal(input.substring(Math.max(0, cursor - CommandSyntaxException.CONTEXT_AMOUNT), cursor)))
.append(Text.literal(input.substring(cursor)).formatted(Formatting.RED, Formatting.UNDERLINE))
.append(Text.translatable("command.context.here").formatted(Formatting.RED, Formatting.ITALIC));
return text;
}
public static LiteralArgumentBuilder<FabricClientCommandSource> literal (String name) { return LiteralArgumentBuilder.<FabricClientCommandSource>literal(name); }
public static <T> RequiredArgumentBuilder<FabricClientCommandSource, T> argument (String name, ArgumentType<T> type) { return RequiredArgumentBuilder.<FabricClientCommandSource, T>argument(name, type); }
static {
TestCommand.register(dispatcher);
CoreCommand.register(dispatcher);
UsernameCommand.register(dispatcher);
}
}

View file

@ -1,79 +1,79 @@
package land.chipmunk.chipmunkmod.commands;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.context.CommandContext;
import static land.chipmunk.chipmunkmod.command.CommandManager.literal;
import static land.chipmunk.chipmunkmod.command.CommandManager.argument;
import static com.mojang.brigadier.arguments.StringArgumentType.greedyString;
import static com.mojang.brigadier.arguments.StringArgumentType.getString;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import net.minecraft.text.Text;
import net.minecraft.nbt.NbtCompound;
import java.util.concurrent.CompletableFuture;
import land.chipmunk.chipmunkmod.modules.CommandCore;
public class CoreCommand {
public static void register (CommandDispatcher<FabricClientCommandSource> dispatcher) {
dispatcher.register(
literal("core")
.then(
literal("run")
.then(
argument("command", greedyString())
.executes(c -> run(c))
)
)
.then(
literal("runTracked")
.then(
argument("command", greedyString())
.executes(c -> runTracked(c))
)
)
.then(literal("refill").executes(c -> refill(c)))
.then(literal("move").executes(c -> move(c)))
);
}
public static int run (CommandContext<FabricClientCommandSource> context) {
CommandCore.INSTANCE.run(getString(context, "command"));
return Command.SINGLE_SUCCESS;
}
public static int runTracked (CommandContext<FabricClientCommandSource> context) {
final FabricClientCommandSource source = context.getSource();
final String command = getString(context, "command");
final CompletableFuture<NbtCompound> future = CommandCore.INSTANCE.runTracked(command);
future.thenApply(tag -> {
try {
final String output = tag.getString("LastOutput");
if (output != null) source.sendFeedback(Text.Serializer.fromJson(output));
} catch (Exception ignored) {
}
return tag;
});
return Command.SINGLE_SUCCESS;
}
public static int refill (CommandContext<FabricClientCommandSource> context) {
CommandCore.INSTANCE.refill();
return Command.SINGLE_SUCCESS;
}
public static int move (CommandContext<FabricClientCommandSource> context) {
final FabricClientCommandSource source = context.getSource();
CommandCore.INSTANCE.move(source.getClient().player.getPos());
return Command.SINGLE_SUCCESS;
}
}
package land.chipmunk.chipmunkmod.commands;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.context.CommandContext;
import static land.chipmunk.chipmunkmod.command.CommandManager.literal;
import static land.chipmunk.chipmunkmod.command.CommandManager.argument;
import static com.mojang.brigadier.arguments.StringArgumentType.greedyString;
import static com.mojang.brigadier.arguments.StringArgumentType.getString;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import net.minecraft.text.Text;
import net.minecraft.nbt.NbtCompound;
import java.util.concurrent.CompletableFuture;
import land.chipmunk.chipmunkmod.modules.CommandCore;
public class CoreCommand {
public static void register (CommandDispatcher<FabricClientCommandSource> dispatcher) {
dispatcher.register(
literal("core")
.then(
literal("run")
.then(
argument("command", greedyString())
.executes(c -> run(c))
)
)
.then(
literal("runTracked")
.then(
argument("command", greedyString())
.executes(c -> runTracked(c))
)
)
.then(literal("refill").executes(c -> refill(c)))
.then(literal("move").executes(c -> move(c)))
);
}
public static int run (CommandContext<FabricClientCommandSource> context) {
CommandCore.INSTANCE.run(getString(context, "command"));
return Command.SINGLE_SUCCESS;
}
public static int runTracked (CommandContext<FabricClientCommandSource> context) {
final FabricClientCommandSource source = context.getSource();
final String command = getString(context, "command");
final CompletableFuture<NbtCompound> future = CommandCore.INSTANCE.runTracked(command);
future.thenApply(tag -> {
try {
final String output = tag.getString("LastOutput");
if (output != null) source.sendFeedback(Text.Serializer.fromJson(output));
} catch (Exception ignored) {
}
return tag;
});
return Command.SINGLE_SUCCESS;
}
public static int refill (CommandContext<FabricClientCommandSource> context) {
CommandCore.INSTANCE.refill();
return Command.SINGLE_SUCCESS;
}
public static int move (CommandContext<FabricClientCommandSource> context) {
final FabricClientCommandSource source = context.getSource();
CommandCore.INSTANCE.move(source.getClient().player.getPos());
return Command.SINGLE_SUCCESS;
}
}

View file

@ -1,24 +1,24 @@
package land.chipmunk.chipmunkmod.commands;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.context.CommandContext;
import static land.chipmunk.chipmunkmod.command.CommandManager.literal;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import net.minecraft.text.Text;
public class TestCommand {
public static void register (CommandDispatcher<FabricClientCommandSource> dispatcher) {
dispatcher.register(
literal("test")
.executes(c -> helloWorld(c))
);
}
public static int helloWorld (CommandContext<FabricClientCommandSource> context) {
final FabricClientCommandSource source = context.getSource();
source.sendFeedback(Text.literal("Hello, world!"));
return Command.SINGLE_SUCCESS;
}
}
package land.chipmunk.chipmunkmod.commands;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.context.CommandContext;
import static land.chipmunk.chipmunkmod.command.CommandManager.literal;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import net.minecraft.text.Text;
public class TestCommand {
public static void register (CommandDispatcher<FabricClientCommandSource> dispatcher) {
dispatcher.register(
literal("test")
.executes(c -> helloWorld(c))
);
}
public static int helloWorld (CommandContext<FabricClientCommandSource> context) {
final FabricClientCommandSource source = context.getSource();
source.sendFeedback(Text.literal("Hello, world!"));
return Command.SINGLE_SUCCESS;
}
}

View file

@ -1,67 +1,67 @@
package land.chipmunk.chipmunkmod.commands;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.context.CommandContext;
import static com.mojang.brigadier.arguments.StringArgumentType.greedyString;
import static com.mojang.brigadier.arguments.StringArgumentType.getString;
import static land.chipmunk.chipmunkmod.command.CommandManager.literal;
import static land.chipmunk.chipmunkmod.command.CommandManager.argument;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.ConnectScreen;
import net.minecraft.client.gui.screen.TitleScreen;
import net.minecraft.client.gui.screen.multiplayer.MultiplayerScreen;
import net.minecraft.client.network.ServerInfo;
import net.minecraft.client.network.ServerAddress;
import net.minecraft.client.util.Session;
import net.minecraft.text.Text;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
import java.util.Optional;
import land.chipmunk.chipmunkmod.mixin.MinecraftClientAccessor;
public class UsernameCommand {
private static final Session ORIGINAL_SESSION = ((MinecraftClientAccessor) MinecraftClient.getInstance()).session();
private static final SimpleCommandExceptionType USERNAME_TOO_LONG = new SimpleCommandExceptionType(Text.translatable("The specified username is longer than 16 characters"));
public static void register (CommandDispatcher<FabricClientCommandSource> dispatcher) {
dispatcher.register(
literal("username")
.then(
literal("set")
.then(
argument("username", greedyString())
.executes(c -> updateUsername(c))
)
)
.then(
literal("revert")
.executes(c -> updateSession(c, ORIGINAL_SESSION))
)
);
}
public static int updateUsername (CommandContext<FabricClientCommandSource> context) throws CommandSyntaxException {
final String username = getString(context, "username");
if (username.length() > 16) throw USERNAME_TOO_LONG.create();
final Session session = new Session(username, "", "", Optional.empty(), Optional.empty(), Session.AccountType.MOJANG);
return updateSession(context, session);
}
public static int updateSession (CommandContext<FabricClientCommandSource> context, Session session) throws CommandSyntaxException {
final FabricClientCommandSource source = context.getSource();
final MinecraftClient client = source.getClient();
((MinecraftClientAccessor) client).session(session);
// TODO: Put this in a separate class
final ServerInfo info = client.getCurrentServerEntry();
client.world.disconnect();
client.disconnect();
ConnectScreen.connect(new MultiplayerScreen(new TitleScreen()), client, ServerAddress.parse(info.address), info);
return Command.SINGLE_SUCCESS;
}
}
package land.chipmunk.chipmunkmod.commands;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.context.CommandContext;
import static com.mojang.brigadier.arguments.StringArgumentType.greedyString;
import static com.mojang.brigadier.arguments.StringArgumentType.getString;
import static land.chipmunk.chipmunkmod.command.CommandManager.literal;
import static land.chipmunk.chipmunkmod.command.CommandManager.argument;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.ConnectScreen;
import net.minecraft.client.gui.screen.TitleScreen;
import net.minecraft.client.gui.screen.multiplayer.MultiplayerScreen;
import net.minecraft.client.network.ServerInfo;
import net.minecraft.client.network.ServerAddress;
import net.minecraft.client.util.Session;
import net.minecraft.text.Text;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
import java.util.Optional;
import land.chipmunk.chipmunkmod.mixin.MinecraftClientAccessor;
public class UsernameCommand {
private static final Session ORIGINAL_SESSION = ((MinecraftClientAccessor) MinecraftClient.getInstance()).session();
private static final SimpleCommandExceptionType USERNAME_TOO_LONG = new SimpleCommandExceptionType(Text.translatable("The specified username is longer than 16 characters"));
public static void register (CommandDispatcher<FabricClientCommandSource> dispatcher) {
dispatcher.register(
literal("username")
.then(
literal("set")
.then(
argument("username", greedyString())
.executes(c -> updateUsername(c))
)
)
.then(
literal("revert")
.executes(c -> updateSession(c, ORIGINAL_SESSION))
)
);
}
public static int updateUsername (CommandContext<FabricClientCommandSource> context) throws CommandSyntaxException {
final String username = getString(context, "username");
if (username.length() > 16) throw USERNAME_TOO_LONG.create();
final Session session = new Session(username, "", "", Optional.empty(), Optional.empty(), Session.AccountType.MOJANG);
return updateSession(context, session);
}
public static int updateSession (CommandContext<FabricClientCommandSource> context, Session session) throws CommandSyntaxException {
final FabricClientCommandSource source = context.getSource();
final MinecraftClient client = source.getClient();
((MinecraftClientAccessor) client).session(session);
// TODO: Put this in a separate class
final ServerInfo info = client.getCurrentServerEntry();
client.world.disconnect();
client.disconnect();
ConnectScreen.connect(new MultiplayerScreen(new TitleScreen()), client, ServerAddress.parse(info.address), info);
return Command.SINGLE_SUCCESS;
}
}

View file

@ -1,13 +1,13 @@
package land.chipmunk.chipmunkmod.data;
import net.minecraft.util.math.BlockPos;
import lombok.AllArgsConstructor;
import lombok.Data;
// ? Am I reinventing the wheel here?
@AllArgsConstructor
@Data
public class BlockArea {
private BlockPos start;
private BlockPos end;
}
package land.chipmunk.chipmunkmod.data;
import net.minecraft.util.math.BlockPos;
import lombok.AllArgsConstructor;
import lombok.Data;
// ? Am I reinventing the wheel here?
@AllArgsConstructor
@Data
public class BlockArea {
private BlockPos start;
private BlockPos end;
}

View file

@ -1,50 +1,50 @@
package land.chipmunk.chipmunkmod.mixin;
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.callback.CallbackInfo;
import java.util.concurrent.CompletableFuture;
import com.mojang.brigadier.suggestion.Suggestions;
import net.minecraft.client.gui.widget.TextFieldWidget;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import net.minecraft.client.MinecraftClient;
import land.chipmunk.chipmunkmod.command.CommandManager;
@Mixin(net.minecraft.client.gui.screen.ChatInputSuggestor.class)
public class ChatInputSuggestorMixin {
@Shadow
CompletableFuture<Suggestions> pendingSuggestions;
@Shadow
public void show (boolean narrateFirstSuggestion) {}
@Shadow
final TextFieldWidget textField;
public ChatInputSuggestorMixin () {
textField = null;
}
@Inject(at = @At("TAIL"), method = "refresh()V")
public void onRefresh (CallbackInfo ci) {
final String text = this.textField.getText();
final int cursor = this.textField.getCursor();
if (cursor < CommandManager.prefix.length() || !text.startsWith(CommandManager.prefix)) return;
final StringReader reader = new StringReader(text);
reader.setCursor(CommandManager.prefix.length()); // Skip the prefix
final CommandDispatcher<FabricClientCommandSource> dispatcher = CommandManager.dispatcher;
final MinecraftClient client = MinecraftClient.getInstance();
final FabricClientCommandSource commandSource = (FabricClientCommandSource) client.getNetworkHandler().getCommandSource();
pendingSuggestions = dispatcher.getCompletionSuggestions(dispatcher.parse(reader, commandSource), cursor);
show(true);
}
}
package land.chipmunk.chipmunkmod.mixin;
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.callback.CallbackInfo;
import java.util.concurrent.CompletableFuture;
import com.mojang.brigadier.suggestion.Suggestions;
import net.minecraft.client.gui.widget.TextFieldWidget;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import net.minecraft.client.MinecraftClient;
import land.chipmunk.chipmunkmod.command.CommandManager;
@Mixin(net.minecraft.client.gui.screen.ChatInputSuggestor.class)
public class ChatInputSuggestorMixin {
@Shadow
CompletableFuture<Suggestions> pendingSuggestions;
@Shadow
public void show (boolean narrateFirstSuggestion) {}
@Shadow
final TextFieldWidget textField;
public ChatInputSuggestorMixin () {
textField = null;
}
@Inject(at = @At("TAIL"), method = "refresh()V")
public void onRefresh (CallbackInfo ci) {
final String text = this.textField.getText();
final int cursor = this.textField.getCursor();
if (cursor < CommandManager.prefix.length() || !text.startsWith(CommandManager.prefix)) return;
final StringReader reader = new StringReader(text);
reader.setCursor(CommandManager.prefix.length()); // Skip the prefix
final CommandDispatcher<FabricClientCommandSource> dispatcher = CommandManager.dispatcher;
final MinecraftClient client = MinecraftClient.getInstance();
final FabricClientCommandSource commandSource = (FabricClientCommandSource) client.getNetworkHandler().getCommandSource();
pendingSuggestions = dispatcher.getCompletionSuggestions(dispatcher.parse(reader, commandSource), cursor);
show(true);
}
}

View file

@ -1,22 +1,22 @@
package land.chipmunk.chipmunkmod.mixin;
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 net.minecraft.client.MinecraftClient;
import land.chipmunk.chipmunkmod.command.CommandManager;
@Mixin(net.minecraft.client.gui.screen.ChatScreen.class)
public class ChatScreenMixin {
@Inject(at = @At("HEAD"), method = "sendMessage", cancellable = true)
public void sendMessage(String chatText, boolean addToHistory, CallbackInfoReturnable<Boolean> cir) {
if (chatText.startsWith(CommandManager.prefix)) {
CommandManager.executeCommand(chatText.substring(CommandManager.prefix.length()));
if (addToHistory) MinecraftClient.getInstance().inGameHud.getChatHud().addToMessageHistory(chatText);
cir.setReturnValue(true);
}
}
}
package land.chipmunk.chipmunkmod.mixin;
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 net.minecraft.client.MinecraftClient;
import land.chipmunk.chipmunkmod.command.CommandManager;
@Mixin(net.minecraft.client.gui.screen.ChatScreen.class)
public class ChatScreenMixin {
@Inject(at = @At("HEAD"), method = "sendMessage", cancellable = true)
public void sendMessage(String chatText, boolean addToHistory, CallbackInfoReturnable<Boolean> cir) {
if (chatText.startsWith(CommandManager.prefix)) {
CommandManager.executeCommand(chatText.substring(CommandManager.prefix.length()));
if (addToHistory) MinecraftClient.getInstance().inGameHud.getChatHud().addToMessageHistory(chatText);
cir.setReturnValue(true);
}
}
}

View file

@ -1,20 +1,20 @@
package land.chipmunk.chipmunkmod.mixin;
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.CallbackInfo;
import net.minecraft.client.network.ClientPlayNetworkHandler;
import net.minecraft.text.Text;
import land.chipmunk.chipmunkmod.modules.CommandCore;
import land.chipmunk.chipmunkmod.modules.SelfCare;
@Mixin(net.minecraft.network.ClientConnection.class)
public class ClientConnectionMixin {
@Inject(at = @At("HEAD"), method = "disconnect", cancellable = true)
public void disconnect (Text disconnectReason, CallbackInfo ci) {
if (disconnectReason == ClientPlayNetworkHandlerAccessor.chatValidationFailedText()) {
ci.cancel();
}
}
}
package land.chipmunk.chipmunkmod.mixin;
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.CallbackInfo;
import net.minecraft.client.network.ClientPlayNetworkHandler;
import net.minecraft.text.Text;
import land.chipmunk.chipmunkmod.modules.CommandCore;
import land.chipmunk.chipmunkmod.modules.SelfCare;
@Mixin(net.minecraft.network.ClientConnection.class)
public class ClientConnectionMixin {
@Inject(at = @At("HEAD"), method = "disconnect", cancellable = true)
public void disconnect (Text disconnectReason, CallbackInfo ci) {
if (disconnectReason == ClientPlayNetworkHandlerAccessor.chatValidationFailedText()) {
ci.cancel();
}
}
}

View file

@ -1,11 +1,11 @@
package land.chipmunk.chipmunkmod.mixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import net.minecraft.text.Text;
@Mixin(net.minecraft.client.network.ClientPlayNetworkHandler.class)
public interface ClientPlayNetworkHandlerAccessor {
@Accessor("CHAT_VALIDATION_FAILED_TEXT")
public static Text chatValidationFailedText () { throw new AssertionError(); }
}
package land.chipmunk.chipmunkmod.mixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import net.minecraft.text.Text;
@Mixin(net.minecraft.client.network.ClientPlayNetworkHandler.class)
public interface ClientPlayNetworkHandlerAccessor {
@Accessor("CHAT_VALIDATION_FAILED_TEXT")
public static Text chatValidationFailedText () { throw new AssertionError(); }
}

View file

@ -1,16 +1,16 @@
package land.chipmunk.chipmunkmod.mixin;
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.CallbackInfo;
import net.minecraft.network.packet.s2c.play.GameJoinS2CPacket;
import land.chipmunk.chipmunkmod.modules.SelfCare;
@Mixin(net.minecraft.client.network.ClientPlayNetworkHandler.class)
public class ClientPlayNetworkHandlerMixin {
@Inject(method = "onGameJoin", at = @At("TAIL"))
private void onGameJoin (GameJoinS2CPacket packet, CallbackInfo ci) {
SelfCare.INSTANCE.init();
}
}
package land.chipmunk.chipmunkmod.mixin;
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.CallbackInfo;
import net.minecraft.network.packet.s2c.play.GameJoinS2CPacket;
import land.chipmunk.chipmunkmod.modules.SelfCare;
@Mixin(net.minecraft.client.network.ClientPlayNetworkHandler.class)
public class ClientPlayNetworkHandlerMixin {
@Inject(method = "onGameJoin", at = @At("TAIL"))
private void onGameJoin (GameJoinS2CPacket packet, CallbackInfo ci) {
SelfCare.INSTANCE.init();
}
}

View file

@ -1,34 +1,34 @@
package land.chipmunk.chipmunkmod.mixin;
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.callback.CallbackInfo;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.entity.MovementType;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec2f;
import land.chipmunk.chipmunkmod.modules.CommandCore;
@Mixin(ClientPlayerEntity.class)
public class ClientPlayerEntityMixin {
private static MinecraftClient CLIENT = MinecraftClient.getInstance();
@Inject(at = @At("HEAD"), method = "move")
public void move (MovementType type, Vec3d relPos, CallbackInfo ci) {
if ((ClientPlayerEntity) (Object) this != CLIENT.player) return;
final Vec3d position = ((ClientPlayerEntity) (Object) this).getPos().add(relPos);
final ClientWorld world = CLIENT.getNetworkHandler().getWorld();
final BlockPos origin = CommandCore.INSTANCE.origin();
if (origin == null) { CommandCore.INSTANCE.move(position); return; }
final int distance = (int) Math.sqrt(new Vec2f(origin.getX() / 16, origin.getZ() / 16).distanceSquared(new Vec2f((int) position.getX() / 16, (int) position.getZ() / 16)));
if (distance > world.getSimulationDistance()) CommandCore.INSTANCE.move(position);
}
}
package land.chipmunk.chipmunkmod.mixin;
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.callback.CallbackInfo;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.entity.MovementType;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec2f;
import land.chipmunk.chipmunkmod.modules.CommandCore;
@Mixin(ClientPlayerEntity.class)
public class ClientPlayerEntityMixin {
private static MinecraftClient CLIENT = MinecraftClient.getInstance();
@Inject(at = @At("HEAD"), method = "move")
public void move (MovementType type, Vec3d relPos, CallbackInfo ci) {
if ((ClientPlayerEntity) (Object) this != CLIENT.player) return;
final Vec3d position = ((ClientPlayerEntity) (Object) this).getPos().add(relPos);
final ClientWorld world = CLIENT.getNetworkHandler().getWorld();
final BlockPos origin = CommandCore.INSTANCE.origin();
if (origin == null) { CommandCore.INSTANCE.move(position); return; }
final int distance = (int) Math.sqrt(new Vec2f(origin.getX() / 16, origin.getZ() / 16).distanceSquared(new Vec2f((int) position.getX() / 16, (int) position.getZ() / 16)));
if (distance > world.getSimulationDistance()) CommandCore.INSTANCE.move(position);
}
}

View file

@ -1,16 +1,16 @@
package land.chipmunk.chipmunkmod.mixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.gen.Accessor;
import net.minecraft.client.util.Session;
@Mixin(net.minecraft.client.MinecraftClient.class)
public interface MinecraftClientAccessor {
@Accessor("session")
Session session ();
@Mutable
@Accessor("session")
void session (Session session);
}
package land.chipmunk.chipmunkmod.mixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.gen.Accessor;
import net.minecraft.client.util.Session;
@Mixin(net.minecraft.client.MinecraftClient.class)
public interface MinecraftClientAccessor {
@Accessor("session")
Session session ();
@Mutable
@Accessor("session")
void session (Session session);
}

View file

@ -1,147 +1,148 @@
package land.chipmunk.chipmunkmod.modules;
import net.minecraft.client.MinecraftClient;
import net.minecraft.network.ClientConnection;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.network.packet.c2s.play.UpdateCommandBlockC2SPacket;
import net.minecraft.block.entity.CommandBlockBlockEntity;
import net.minecraft.nbt.NbtCompound;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import land.chipmunk.chipmunkmod.data.BlockArea;
public class CommandCore {
private MinecraftClient client;
@Getter @Setter private boolean ready = false;
@Getter @Setter private BlockPos origin;
// TODO: Make it configurable
@Getter private final BlockArea relativeArea = new BlockArea(new BlockPos(0, 0, 0), new BlockPos(15, 0, 15));
@Getter @Setter private BlockPos currentBlockRelative;
public static CommandCore INSTANCE = new CommandCore(MinecraftClient.getInstance());
public CommandCore (MinecraftClient client) {
this.client = client;
}
public void move (Vec3d position) {
if (!ready) {
ready = true;
// for (Listener listener : listeners) listener.ready();
}
origin = new BlockPos(
((int) position.getX() / 16) * 16,
0, // TODO: Use the actual bottom of the world instead of hardcoding to 0
((int) position.getZ() / 16) * 16
);
if (currentBlockRelative == null) currentBlockRelative = new BlockPos(relativeArea.start());
refill();
}
public void refill () {
// final PositionManager position = client.position();
final BlockPos relStart = relativeArea.start();
final BlockPos relEnd = relativeArea.end();
final String command = String.format(
"fill %s %s %s %s %s %s command_block",
relStart.getX() + origin.getX(),
relStart.getY() + origin.getY(),
relStart.getZ() + origin.getZ(),
relEnd.getX() + origin.getX(),
relEnd.getY() + origin.getY(),
relEnd.getZ() + origin.getZ()
);
client.getNetworkHandler().sendChatCommand(command);
}
public void incrementCurrentBlock () {
final BlockPos start = relativeArea.start();
final BlockPos end = relativeArea.end();
int x = currentBlockRelative.getX();
int y = currentBlockRelative.getY();
int z = currentBlockRelative.getZ();
x++;
if (x > end.getX()) {
x = start.getX();
z++;
}
if (z > end.getZ()) {
z = start.getZ();
y++;
}
if (y > end.getY()) {
x = start.getX();
y = start.getY();
z = start.getZ();
}
currentBlockRelative = new BlockPos(x, y, z);
}
public BlockPos currentBlockAbsolute () {
return currentBlockRelative.add(origin);
}
public void run (String command) {
final ClientConnection connection = client.getNetworkHandler().getConnection();
final BlockPos currentBlock = currentBlockAbsolute();
// TODO: Support using repeating command blocks (on kaboom-like servers) (because less packets)
connection.send(new UpdateCommandBlockC2SPacket(currentBlock, "", CommandBlockBlockEntity.Type.REDSTONE, false, false, false));
connection.send(new UpdateCommandBlockC2SPacket(currentBlock, command, CommandBlockBlockEntity.Type.REDSTONE, false, false, true));
incrementCurrentBlock();
}
public CompletableFuture<NbtCompound> runTracked (String command) {
final ClientConnection connection = client.getNetworkHandler().getConnection();
final BlockPos currentBlock = currentBlockAbsolute();
// TODO: Support using repeating command blocks (on kaboom-like servers) (because less packets)
connection.send(new UpdateCommandBlockC2SPacket(currentBlock, "", CommandBlockBlockEntity.Type.SEQUENCE, false, false, false));
connection.send(new UpdateCommandBlockC2SPacket(currentBlock, command, CommandBlockBlockEntity.Type.REDSTONE, true, false, true));
incrementCurrentBlock();
CompletableFuture<NbtCompound> future = new CompletableFuture<NbtCompound>();
final Timer timer = new Timer();
final TimerTask queryTask = new TimerTask() {
public void run () {
client.getNetworkHandler().getDataQueryHandler().queryBlockNbt(currentBlock,
tag -> { future.complete(tag); });
timer.cancel(); // ? Is this necesary?
timer.purge();
}
};
timer.schedule(queryTask, 50);
return future;
}
public void cleanup () {
origin = null;
currentBlockRelative = null;
ready = false;
}
}
package land.chipmunk.chipmunkmod.modules;
import net.minecraft.client.MinecraftClient;
import net.minecraft.network.ClientConnection;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.network.packet.c2s.play.UpdateCommandBlockC2SPacket;
import net.minecraft.block.entity.CommandBlockBlockEntity;
import net.minecraft.nbt.NbtCompound;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import land.chipmunk.chipmunkmod.ChipmunkMod;
import land.chipmunk.chipmunkmod.data.BlockArea;
public class CommandCore {
private MinecraftClient client;
@Getter @Setter private boolean ready = false;
@Getter @Setter private BlockPos origin;
@Getter private final BlockArea relativeArea;
@Getter @Setter private BlockPos currentBlockRelative;
public static CommandCore INSTANCE = new CommandCore(MinecraftClient.getInstance(), ChipmunkMod.CONFIG.core.relativeArea);
public CommandCore (MinecraftClient client, BlockArea relativeArea) {
this.client = client;
this.relativeArea = relativeArea;
}
public void move (Vec3d position) {
if (!ready) {
ready = true;
// for (Listener listener : listeners) listener.ready();
}
origin = new BlockPos(
((int) position.getX() / 16) * 16,
0, // TODO: Use the actual bottom of the world instead of hardcoding to 0
((int) position.getZ() / 16) * 16
);
if (currentBlockRelative == null) currentBlockRelative = new BlockPos(relativeArea.start());
refill();
}
public void refill () {
// final PositionManager position = client.position();
final BlockPos relStart = relativeArea.start();
final BlockPos relEnd = relativeArea.end();
final String command = String.format(
"fill %s %s %s %s %s %s command_block",
relStart.getX() + origin.getX(),
relStart.getY() + origin.getY(),
relStart.getZ() + origin.getZ(),
relEnd.getX() + origin.getX(),
relEnd.getY() + origin.getY(),
relEnd.getZ() + origin.getZ()
);
client.getNetworkHandler().sendChatCommand(command);
}
public void incrementCurrentBlock () {
final BlockPos start = relativeArea.start();
final BlockPos end = relativeArea.end();
int x = currentBlockRelative.getX();
int y = currentBlockRelative.getY();
int z = currentBlockRelative.getZ();
x++;
if (x > end.getX()) {
x = start.getX();
z++;
}
if (z > end.getZ()) {
z = start.getZ();
y++;
}
if (y > end.getY()) {
x = start.getX();
y = start.getY();
z = start.getZ();
}
currentBlockRelative = new BlockPos(x, y, z);
}
public BlockPos currentBlockAbsolute () {
return currentBlockRelative.add(origin);
}
public void run (String command) {
final ClientConnection connection = client.getNetworkHandler().getConnection();
final BlockPos currentBlock = currentBlockAbsolute();
// TODO: Support using repeating command blocks (on kaboom-like servers) (because less packets)
connection.send(new UpdateCommandBlockC2SPacket(currentBlock, "", CommandBlockBlockEntity.Type.REDSTONE, false, false, false));
connection.send(new UpdateCommandBlockC2SPacket(currentBlock, command, CommandBlockBlockEntity.Type.REDSTONE, false, false, true));
incrementCurrentBlock();
}
public CompletableFuture<NbtCompound> runTracked (String command) {
final ClientConnection connection = client.getNetworkHandler().getConnection();
final BlockPos currentBlock = currentBlockAbsolute();
// TODO: Support using repeating command blocks (on kaboom-like servers) (because less packets)
connection.send(new UpdateCommandBlockC2SPacket(currentBlock, "", CommandBlockBlockEntity.Type.SEQUENCE, false, false, false));
connection.send(new UpdateCommandBlockC2SPacket(currentBlock, command, CommandBlockBlockEntity.Type.REDSTONE, true, false, true));
incrementCurrentBlock();
CompletableFuture<NbtCompound> future = new CompletableFuture<NbtCompound>();
final Timer timer = new Timer();
final TimerTask queryTask = new TimerTask() {
public void run () {
client.getNetworkHandler().getDataQueryHandler().queryBlockNbt(currentBlock,
tag -> { future.complete(tag); });
timer.cancel(); // ? Is this necesary?
timer.purge();
}
};
timer.schedule(queryTask, 50);
return future;
}
public void cleanup () {
origin = null;
currentBlockRelative = null;
ready = false;
}
}

View file

@ -1,72 +1,72 @@
package land.chipmunk.chipmunkmod.modules;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientPlayNetworkHandler;
import net.minecraft.client.network.ClientPlayerEntity;
import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import lombok.Getter;
import java.util.Timer;
import java.util.TimerTask;
public class SelfCare {
private final MinecraftClient client;
@Getter private long interval;
private Timer timer = null;
public static final SelfCare INSTANCE = new SelfCare(MinecraftClient.getInstance(), 70L);
public SelfCare (MinecraftClient client, long interval) {
this.client = client;
this.interval = interval;
}
public void init () {
final TimerTask task = new TimerTask() {
public void run () {
tick();
}
};
if (timer != null) cleanup();
timer = new Timer();
timer.schedule(task, interval, interval);
}
public void cleanup () {
if (timer == null) return;
timer.cancel();
timer.purge();
timer = null;
}
public void tick () {
final ClientPlayerEntity player = client.player;
final ClientPlayNetworkHandler networkHandler = client.getNetworkHandler();
if (networkHandler == null) {
cleanup();
return;
}
if (!player.hasPermissionLevel(2)) { if (serverHasCommand("op")) networkHandler.sendChatCommand("op @s[type=player]"); }
else if (!client.player.isCreative()) networkHandler.sendChatCommand("gamemode creative");
}
// TODO: Move this into a separate class related to server info gathering (and yes, I plan on making this d y n a m i c and require little to no configuration for most servers)
private boolean serverHasCommand (String name) {
final ClientPlayNetworkHandler networkHandler = client.getNetworkHandler();
for (CommandNode node : networkHandler.getCommandDispatcher().getRoot().getChildren()) {
if (!(node instanceof LiteralCommandNode)) continue;
final LiteralCommandNode literal = (LiteralCommandNode) node;
if (literal.getLiteral().equals(name)) return true;
}
return false;
}
}
package land.chipmunk.chipmunkmod.modules;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientPlayNetworkHandler;
import net.minecraft.client.network.ClientPlayerEntity;
import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import lombok.Getter;
import java.util.Timer;
import java.util.TimerTask;
public class SelfCare {
private final MinecraftClient client;
@Getter private long interval;
private Timer timer = null;
public static final SelfCare INSTANCE = new SelfCare(MinecraftClient.getInstance(), 70L);
public SelfCare (MinecraftClient client, long interval) {
this.client = client;
this.interval = interval;
}
public void init () {
final TimerTask task = new TimerTask() {
public void run () {
tick();
}
};
if (timer != null) cleanup();
timer = new Timer();
timer.schedule(task, interval, interval);
}
public void cleanup () {
if (timer == null) return;
timer.cancel();
timer.purge();
timer = null;
}
public void tick () {
final ClientPlayerEntity player = client.player;
final ClientPlayNetworkHandler networkHandler = client.getNetworkHandler();
if (networkHandler == null) {
cleanup();
return;
}
if (!player.hasPermissionLevel(2)) { if (serverHasCommand("op")) networkHandler.sendChatCommand("op @s[type=player]"); }
else if (!client.player.isCreative()) networkHandler.sendChatCommand("gamemode creative");
}
// TODO: Move this into a separate class related to server info gathering (and yes, I plan on making this d y n a m i c and require little to no configuration for most servers)
private boolean serverHasCommand (String name) {
final ClientPlayNetworkHandler networkHandler = client.getNetworkHandler();
for (CommandNode node : networkHandler.getCommandDispatcher().getRoot().getChildren()) {
if (!(node instanceof LiteralCommandNode)) continue;
final LiteralCommandNode literal = (LiteralCommandNode) node;
if (literal.getLiteral().equals(name)) return true;
}
return false;
}
}

View file

@ -0,0 +1,12 @@
{
"commands": {
"prefix": "."
},
"core": {
"relativeArea": {
"start": { "x": 0, "y": 0, "z": 0 },
"end": { "x": 15, "y": 0, "z": 15 }
}
}
}