User Tools

Site Tools


tutorial:command_argument_types

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
tutorial:command_argument_types [2023/11/18 12:27] solidblocktutorial:command_argument_types [2024/04/15 07:21] (current) – [Custom argument serializer] solidblock
Line 64: Line 64:
         }         }
         // ...         // ...
 +    }
 +</code>
 +
 +===== Defining Argument examples =====
 +Sometimes argument should have examples. It is usually stored in an immutable collection as a static final field. Examples are used to detect ambiguities.
 +<code java>
 +    private static final Collection<String> EXAMPLES = List.of(
 +        "765e5d33-c991-454f-8775-b6a7a394c097", // i509VCB: Username The_1_gamers
 +        "069a79f4-44e9-4726-a5be-fca90e38aaf5", // Notch
 +        "61699b2e-d327-4a01-9f1e-0ea8c3f06bc6"  // Dinnerbone
 +    );
 +
 +    @Override
 +    public Collection<String> getExamples() {
 +        // Brigadier has support to show examples for what the argument should look like,
 +        // this should contain a Collection of only the argument this type will return.
 +        // This is mainly used to detect ambiguity, which means an argument of this type may be parsed as another type.
 +        return EXAMPLES;
     }     }
 </code> </code>
Line 69: Line 87:
 ===== Register the argument type ===== ===== Register the argument type =====
  
-The ''ArgumentType'' is done, however your client will refuse the parse the argument and throw an error. This is because the server will tell the client what argument type the command node is. And the client will not parse any argument types which it does not know how to parse. To fix this we need to register an ''ArgumentSerializer'' within your ''ModInitializer''. For more complex argument types, you may need to create your own ''ArgumentSerializer''.+The ''ArgumentType'' is done, however your client will refuse the parse the argument and throw an error. This is because the server will tell the client what argument type the command node is. And the client will not parse any argument types which it does not know how to parse. To fix this we need to register an ''ArgumentSerializer'' within your ''ModInitializer''. For simple argument types (which has a constructor takes no parameter or takes one ''CommandRegistryAccess''), you can simply use ''ConstantArgumentSerializer.of''. For more complex argument types, you may need to create your own ''ArgumentSerializer''.
  
 <code java [enable_line_numbers="true"]> <code java [enable_line_numbers="true"]>
-ArgumentTypeRegistry.registerArgumentType(new Identifier("tutorial", "uuid"), UuidArgumentType.class, ConstantArgumentSerializer.of(UuidArgumentType::uuid)); +ArgumentTypeRegistry.registerArgumentType( 
 +  new Identifier("tutorial", "uuid"), 
 +  UuidArgumentType.class, ConstantArgumentSerializer.of(UuidArgumentType::uuid)); 
 // The argument should be what will create the ArgumentType. // The argument should be what will create the ArgumentType.
 </code> </code>
  
-And here is the whole code of the argument type:+===== Example of a whole argument type =====
  
 <file java UuidArgumentType.java [enable_line_numbers="true"]> <file java UuidArgumentType.java [enable_line_numbers="true"]>
Line 148: Line 168:
         // Brigadier has support to show examples for what the argument should look like,         // Brigadier has support to show examples for what the argument should look like,
         // this should contain a Collection of only the argument this type will return.         // this should contain a Collection of only the argument this type will return.
-        // This is mainly used to calculate ambiguous commands which share the exact same.+        // This is mainly used to detect ambiguity, which means an argument of this type may be parsed as another type.
         return EXAMPLES;         return EXAMPLES;
     }     }
Line 154: Line 174:
 </file> </file>
  
 +===== Specifying suggestions =====
 +
 +Many argument types supports suggestions. Unlike custom suggestions defined in registration of commands, these suggestions are always applied when there is no custom suggestions. Suggestions can be calculated in client. For example, ''BlockPosArgumentType'' may suggest the position of the client-side cross-hair target, and ''EntityArgumentType'' may suggest the excat UUID of the entity that the cross-hair targets.
 +
 +For how to provide suggestions, see [[command suggestions]]. In this example, we suggest UUIDs of all players in the server, as well as the cross-hair target entity in client.
 +
 +<code java>
 +  @Override
 +  public <S> CompletableFuture<Suggestions> listSuggestions(CommandContext<S> context, SuggestionsBuilder builder) {
 +    final String remaining = builder.getRemaining();
 +    if (context.getSource() instanceof FabricClientCommandSource clientCommandSource) {
 +      if (clientCommandSource.getClient().crosshairTarget instanceof EntityHitResult entityHitResult) {
 +        final UUID uuid = entityHitResult.getEntity().getUuid();
 +        if (CommandSource.shouldSuggest(remaining, uuid.toString())) {
 +          builder.suggest(uuid.toString());
 +        }
 +      }
 +      return CommandSource.suggestMatching(Collections2.transform(clientCommandSource.getClient().getNetworkHandler().getPlayerUuids(), UUID::toString), builder);
 +    }
 +
 +    if (context.getSource() instanceof ServerCommandSource source) {
 +      return CommandSource.suggestMatching(Lists.transform(source.getServer().getPlayerManager().getPlayerList(), Entity::getUuidAsString), builder);
 +    }
 +    
 +    return builder.buildFuture();
 +  }
 +</code>
 +
 +===== Custom argument serializer ======
 +In the example above, the UUID is simple. If the argument is complex, how to make it correctly send to and understood by the client? This is where ''ArgumentSerializer'' come into use.
 +
 +''ArgumentSerializer'' has generic with two type parameters:
 +  * The ''ArgumentType''.
 +  * The ''ArgumentSerializer.ArgumentTypeProperties<A>'', where ''A'' is the first type parameter. It is usally an immutable object (can be records) containing the basic information (not including ''CommandRegistryAccess'') of the argument type. In some cases, when the ''ArgumentType'' is immutable and does not contain ''CommandRegistryAccess'', it itself can extend ''ArgumentSerializer.ArgumentTypeProperties<A>''.
 +
 +In this example, we create a new complicated argument type, which contains a boolean value and an integer value. ''CommandRegistryAccess'' is required in this case.
 +
 +<code java>
 +public record ExampleArgumentType(CommandRegistryAccess commandRegistryAccess, boolean booleanValue, int intValue) implements ArgumentType<String> {
 +  @Override
 +  public String parse(StringReader reader) throws CommandSyntaxException {
 +    ...
 +  }
 +  
 +  public static class Serializer implements ArgumentSerializer<ExampleArgumentType, Serializer.Properties> {
 +    @Override
 +    public void writePacket(Properties properties, PacketByteBuf buf) {
 +      // Writes the basic properties to a packet. You should ensure all properties
 +      // can be in some ways stored in the packet.
 +      buf.writeBoolean(properties.booleanValue).writeInt(properties.intValue);
 +    }
 +
 +    @Override
 +    public Properties fromPacket(PacketByteBuf buf) {
 +      // Reads the information in the packet. It returns the ''ArgumentSerializer.ArgumentTypeProperties'' instead of ''ArgumentType''.
 +      return new Properties(buf.readBoolean(), buf.readInt());
 +    }
 +
 +    @Override
 +    public void writeJson(Properties properties, JsonObject json) {
 +      // Present the argument type in the format of JSON.
 +      json.addProperty("booleanValue", properties.booleanValue);
 +      json.addProperty("intValue", properties.intValue);
 +    }
 +
 +    @Override
 +    public Properties getArgumentTypeProperties(ExampleArgumentType argumentType) {
 +      return new Properties(argumentType.booleanValue, argumentType.intValue);
 +    }
 +
 +    public record Properties(boolean booleanValue, int intValue) implements ArgumentTypeProperties<ExampleArgumentType> {
 +      @Override
 +      public ExampleArgumentType createType(CommandRegistryAccess commandRegistryAccess) {
 +        // Only in this method, ''CommandRegistryAccess'' is provided, 
 +        // and the argument type that require ''CommandRegistryAccess'' can be created.
 +        return new ExampleArgumentType(commandRegistryAccess, booleanValue, intValue);
 +      }
 +
 +      @Override
 +      public ArgumentSerializer<ExampleArgumentType, Serializer.Properties> getSerializer() {
 +        // Do not create a new ''Serializer'' object here.
 +        return Serializer.this;
 +      }
 +    }
 +  }
 +}
 +</code>
 +
 +And now you can register it like this:
 +<code java>
 +ArgumentTypeRegistry.registerArgumentType(new Identifier("tutorial", "example"), ExampleArgumentType.class, new ExampleArgumentType.Serializer());
 +</code>
 +
 +==== Another possible way to define serializer ====
 +
 +If the argument does not require ''CommandRegistryAccess'', it itself may extend ''ArgumentSerializer.ArgumentTypeProperties'':
 +
 +<code java>
 +public record ExampleArgumentType(boolean booleanValue, int intValue) implements ArgumentType<String>, ArgumentSerializer.ArgumentTypeProperties<ExampleArgumentType> {
 +  @Override
 +  public String parse(StringReader reader) throws CommandSyntaxException {
 +    ...
 +  }
 +
 +  @Override
 +  public ExampleArgumentType createType(CommandRegistryAccess commandRegistryAccess) {
 +    return this;
 +  }
 +
 +  @Override
 +  public ArgumentSerializer<ExampleArgumentType, ?> getSerializer() {
 +    // always return a same object here to avoid failing to serialize.
 +    return Serializer.INSTANCE;
 +  }
 +
 +  public static class Serializer implements ArgumentSerializer<ExampleArgumentType, ExampleArgumentType> {
 +    public static final Serializer INSTANCE = new Serializer();
 +    private Serializer() {}
 +  
 +    @Override
 +    public void writePacket(ExampleArgumentType properties, PacketByteBuf buf) {
 +      buf.writeBoolean(properties.booleanValue).writeInt(properties.intValue);
 +    }
 +
 +    @Override
 +    public ExampleArgumentType fromPacket(PacketByteBuf buf) {
 +      return new ExampleArgumentType(buf.readBoolean(), buf.readInt());
 +    }
 +
 +    @Override
 +    public void writeJson(ExampleArgumentType properties, JsonObject json) {
 +      json.addProperty("booleanValue", properties.booleanValue);
 +      json.addProperty("intValue", properties.intValue);
 +    }
 +
 +    @Override
 +    public ExampleArgumentType getArgumentTypeProperties(ExampleArgumentType argumentType) {
 +      return argumentType;
 +    }
 +  }
 +}
 +</code>
 +
 +And now you can register it like this:
 +<code java>
 +ArgumentTypeRegistry.registerArgumentType(new Identifier("tutorial", "example"), ExampleArgumentType.class, ExampleArgumentType.Serializer.INSTANCE);
 +</code>
tutorial/command_argument_types.1700310467.txt.gz · Last modified: 2023/11/18 12:27 by solidblock