Jump to content

Frozen Storm

Members
  • Posts

    9
  • Joined

  • Last visited

Frozen Storm's Achievements

Tree Puncher

Tree Puncher (2/8)

0

Reputation

  1. I managed to accomplish this by simply reregistering the command to a command that simply prints an error message to the player: dispatcher.register(Commands.literal(alias) .executes(commandContext -> { Minecraft.getInstance().player.sendSystemMessage(Component.literal("Alias " + alias " has been removed.")); return Command.SINGLE_SUCCESS; }) );
  2. Yes, I'm trying to remove a command during runtime. The command is added (registered) either at game start or during runtime with the registerAlias method, and I'd like to be able to remove it without having to restart the game.
  3. I'm trying to unregister a command I'd previously registered with the CommandDispatcher using something like private static int registerAlias(CommandDispatcher<CommandSourceStack> dispatcher, String alias, String command) { dispatcher.register(Commands.literal(alias) .executes((commandContext) -> { dispatcher.execute(command, commandContext.getSource()); return Command.SINGLE_SUCCESS; }) ); return Command.SINGLE_SUCCESS; } The dispatcher doesn't seem to have an unregister method. The closest I found was the removeCommand method here, but that seems to be a Bukkit thing. So I tried another solution that does something similar with reflection: @SuppressWarnings("unchecked") private static int unregisterAlias(CommandDispatcher<CommandSourceStack> dispatcher, String alias) { Object node = dispatcher.getRoot().getChild(alias); if (node != null) { try { if (node instanceof LiteralCommandNode<?>) { Field literals = CommandNode.class.getDeclaredField("literals"); literals.setAccessible(true); ((Map<String, ?>) literals.get(node)).remove(alias, node); } else if (node instanceof ArgumentCommandNode<?, ?>) { Field arguments = CommandNode.class.getDeclaredField("arguments"); arguments.setAccessible(true); ((Map<String, ?>) arguments.get(node)).remove(alias, node); } Field children = CommandNode.class.getDeclaredField("children"); children.setAccessible(true); ((Map<String, ?>) children.get(node)).remove(alias, node); } catch (ReflectiveOperationException e) { throw new RuntimeException("Error removing command: " + alias, e); } } return Command.SINGLE_SUCCESS; } However, this didn't work. The method was executed without errors (I checked with the Debugger), but the command was still working ingame, meaning it wasn't actually removed from the dispatcher. How would I actually remove a registered command from a CommandDispatcher?
  4. I managed to get basic aliases to work using: private static int registerAlias(CommandDispatcher<CommandSourceStack> dispatcher, String alias, String command) { dispatcher.register(Commands.literal(alias) .executes((commandContext) -> { dispatcher.execute(command, commandContext.getSource()); return Command.SINGLE_SUCCESS; }) ); } I also managed to add the possibility for arguments in the alias, and my entire method looks like this now: private static int registerAlias(CommandDispatcher<CommandSourceStack> dispatcher, String alias, String command) { Pattern argPattern = Pattern.compile("\\$\\{?(\\d+)}?"); if (argPattern.matcher(command).find()) { dispatcher.register(Commands.literal(alias) .then(Commands.argument("args", StringArgumentType.greedyString()) .executes((commandContext) -> { String commandString = command; String argString = StringArgumentType.getString(commandContext, "args"); String[] args = ArgumentTokenizer.tokenize(argString).toArray(new String[0]); commandString = argPattern.matcher(commandString).replaceAll( match -> args[Integer.parseInt(match.group(1))-1] ); dispatcher.execute(commandString, commandContext.getSource()); return Command.SINGLE_SUCCESS; })) ); } else { dispatcher.register(Commands.literal(alias) .executes((commandContext) -> { dispatcher.execute(command, commandContext.getSource()); return Command.SINGLE_SUCCESS; }) ); } return Command.SINGLE_SUCCESS; } This way if a command contains any $1, $2, $10, ${1}, etc., it gets registered as an alias with arguments, otherwise it gets registered as a standalone. ArgumentTokenizer is the class I found here.
  5. I'm trying to create a mod that lets me define command aliases in MC. I currently have the following code: package com.extended_ui.commands; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.apache.commons.io.IOUtils; import com.mojang.brigadier.Command; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.arguments.StringArgumentType; import net.minecraft.client.Minecraft; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; import net.minecraft.network.chat.Component; public class Alias { private static Map<String, String> aliases = new HashMap<String, String>(); public static void registerCommands(CommandDispatcher<CommandSourceStack> dispatcher) { loadAliasesFromFiles(); for (Map.Entry<String, String> entry : aliases.entrySet()) { registerAlias(dispatcher, entry.getKey(), entry.getValue()); } dispatcher.register(Commands.literal("alias") .then(Commands.literal("-l") .executes((command) -> { return listAliases(); })) .then(Commands.literal("-r") .then(Commands.argument("alias", StringArgumentType.word()) .executes((command) -> { return removeAlias(dispatcher, StringArgumentType.getString(command, "alias")); }))) .then(Commands.argument("alias", StringArgumentType.word()) .then(Commands.argument("command", StringArgumentType.greedyString()) .executes((command) -> { return addAlias(dispatcher, StringArgumentType.getString(command, "alias"), StringArgumentType.getString(command, "command")); }))) ); } private static int listAliases() { Minecraft mc = Minecraft.getInstance(); for (Map.Entry<String, String> entry : aliases.entrySet()) { mc.player.sendSystemMessage(Component.literal(entry.getKey() + ": " + entry.getValue())); } return Command.SINGLE_SUCCESS; } private static int addAlias(CommandDispatcher<CommandSourceStack> dispatcher, String alias, String command) { // Add alias to the map of aliases aliases.put(alias, command); // Save alias to file try { new File("config/aliases").mkdirs(); Files.write(Paths.get("config/aliases/" + alias), Collections.singletonList(command), StandardCharsets.UTF_8); } catch (IOException e) { e.printStackTrace(); } // Register new alias in the event dispatcher return registerAlias(dispatcher, alias, command); } private static int registerAlias(CommandDispatcher<CommandSourceStack> dispatcher, String alias, String command) { // dispatcher.register(Commands.literal(alias) // .then(Commands.argument("args", StringArgumentType.greedyString()) // .redirect(command))); return Command.SINGLE_SUCCESS; } private static int removeAlias(CommandDispatcher<CommandSourceStack> dispatcher, String alias) { // Remove alias from the map of aliases aliases.remove(alias); // Remove alias file new File("config/aliases/" + alias).delete(); // Remove alias from the event dispatcher // dispatcher.unregister(Commands.literal(alias)); // Not sure the dispatcher supports some way of unregistering commands. If it doesn't, then removing an alias properly will require a game restart return Command.SINGLE_SUCCESS; } private static void loadAliasesFromFiles() { File dir = new File("config/aliases"); if (!(dir.isDirectory())) return; for (File file : dir.listFiles()) { String alias = file.getName(); try (FileInputStream inputStream = new FileInputStream("foo.txt")) { String command = IOUtils.toString(inputStream, StandardCharsets.UTF_8).trim(); aliases.put(alias, command); } catch (IOException e) { e.printStackTrace(); } } } } The methods for adding, removing, loading, and listing aliases all work properly (i.e., they add/remove/list the aliases in the aliases Map, and save/remove/load the aliases to/from files). However, right now registering an alias doesn't work, as I can't find a way to register an alias to simply execute the command as if it was sent in the game console. How would I implement the registerAlias method to properly register such a simple forward with the CommandDispatcher?
  6. In order to debug this, I hacked the private FishingBobberEntity#func_234603_b_ to be callable using java reflection, and printed out its value for the block the bobber is in and for the player's targeted block (objectMouseOver). This does actually print out seemingly correct values (it prints out false for blocks near the shore and true for blocks further out). I rechecked the code in FishingBobberEntity#tick and it seems the check only happens when a fish is catchable (ticksCatchable > 0 || ticksCatchableDelay > 0), otherwise it just returns true. I thought this explained the problem, but it's still erroneously printing true near the shore, even when the blobber gets pulled into the water (indicating a fish can be caught). I guess I'll just use the hacky solution for now.
  7. So I've dived a bit more into the source code. First off, there seem to be 3 ways to obtain an instance of FishingPredicate: A private constructor and a public static factory method (func_234640_a_) that seems to just invoke this constructor (whatever the reason for this particular design pattern might be). A serialization (func_234637_a_) and deserialization (func_234639_a_) method that handle JSONification of the object. An instance field (field_234635_a_) which seems to just be a fallback placeholder (similar to ANY in many other Predicates). Of these three only the 1st one (func_234640_a_) seems relevant to me, since I don't know where I'd get the required JSON for 2, and 3 just simply always returns true in the test method, which probably isn't what I want. The 1st one is also used in FishingLootTables, namely with the argument true. Additionally the class contains the boolean field_234636_b_, which seems to determine what value of in_open_water the predicate is looking for. Given the call in FishingLootTables I mentioned above, this would make sense, since treasure loot should only be added if in_open_water is true. Last, the class contains the test method (func_234638_a_), which for a FishingBobberEntity argument (such as mc.player.fishingBobber in my code) returns whether the above field_234636_b_ is equal to the result of a call to the argument's func_234605_g_(). This method simply returns the value of a private boolean field (FishingBobberEntity#field_234595_aq_), which in turn gets set in FishingBobberEntity#tick with a check that seems like it could be for open water. Unfortunately I can't access the function FishingBobberEntity#func_234603_b_ (which is part of this check) in my code, as it's private, but I believe that performs the open water check for a specific block. The most sensible explanation for me would therefore be that FishingBobberEntity#func_234605_g_ returns whether the player is currently fishing. However, I decided to display the results of the test method in-game, and it didn't work as expected. Namely, my mod now contains the following code: @SubscribeEvent public void onRenderOverlay(RenderGameOverlayEvent.Text event) { if (mc.gameSettings.showDebugInfo) { ArrayList<String> leftText = event.getLeft(); ArrayList<String> rightText = event.getRight(); if (mc.player.fishingBobber != null) { // if player is currently fishing FishingPredicate alwaysTrue = FishingPredicate.field_234635_a_; // this will just always return true FishingPredicate notIOW = FishingPredicate.func_234640_a_(false); // this checks if in_open_water is false FishingPredicate isIOW = FishingPredicate.func_234640_a_(true); // this checks if in_open_water is true rightText.add(Boolean.toString(mc.player.fishingBobber.func_234605_g_())); // is bobber currently in open water? rightText.add(Boolean.toString(alwaysTrue.func_234638_a_(mc.player.fishingBobber))); rightText.add(Boolean.toString(notIOW.func_234640_a_(false).func_234638_a_(mc.player.fishingBobber))); rightText.add(Boolean.toString(isIOW.func_234640_a_(true).func_234638_a_(mc.player.fishingBobber))); } } } which checks the values of the methods I described. Unfortunately, func_234605_g_ just always returns true in-game, regardless of whether I'm fishing out in deep open water, or in a 2x2x1 pool I dug, or even on land. The predicates return values that fit this (so always returning true, false, true in the above code).
  8. I've looked at FishingPredicate, but both the documentation and the class definition I found with ctrl+click in Eclipse have some very confusing field and function names, so I'm having a little trouble with how I'm supposed to use it. I've found a (somewhat old) post that explains briefly how Predicates work in general (although looking at the docs of other predicates, it seems the apply method has been renamed to test - or maybe this is specific to Forge API predicates?), but I'm not sure how I'm supposed to initialise FishingPredicate (or otherwise obtain it from the player/water block/fishing rod/...).
  9. According to the patch notes one of the recent updates added the in_open_water parameter. Is there a way to print the value of this parameter to the debug screen (F3) either for the currently targeted block or for the bobber when fishing? I've managed to make a mod that adds something to the debug screen when the player is fishing: @Mod("extended_debug") public class ExtendedDebug { // Directly reference a log4j logger. private static final Logger LOGGER = LogManager.getLogger(); private static final Minecraft mc = Minecraft.getInstance(); public ExtendedDebug() { // Register ourselves for server and other game events we are interested in MinecraftForge.EVENT_BUS.register(this); } @SubscribeEvent public void onRenderOverlay(RenderGameOverlayEvent.Text event) { if (mc.gameSettings.showDebugInfo) { ArrayList<String> leftText = event.getLeft(); ArrayList<String> rightText = event.getRight(); if (mc.player.fishingBobber != null) { rightText.add(""); rightText.add("HELLO WORLD!"); } } } } This properly adds HELLO WORLD to the debug screen when the player is fishing, but I can't find anything in FishingBobberEntity to get information about whether the player is fishing in open water.
×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.