JorelAli / CommandAPI

A Bukkit/Spigot API for the command UI introduced in Minecraft 1.13
https://commandapi.jorel.dev
MIT License
504 stars 60 forks source link

The CommandAPI's function execution should support function parameters (macros) #525

Open JorelAli opened 4 months ago

JorelAli commented 4 months ago

Description

In Minecraft 1.20.2, Minecraft added support for "macros" (more info here) which lets you run functions with parameters:

/function foo:bar {key_1:"Example String", key_2:10}
# function foo:bar
say This is a normal non-macro command
$say This is a macro line, using $(key_1)!
$teleport @s ~ ~$(key_2) ~

Expected code

NBT structure

Function arguments are represented as NBT compounds. Spigot does not have a data structure to represent an NBT compound, so we have options on how we want to represent this data structure.

Say we have the following NBT compound:

{
  name1: 123,
  name2: "sometext1",
  name3: {
    subname1: 456,
    subname2:"sometext2"
  }
}

CommandAPI NBT compound structure

We could create our own NBT compound data structure:

public class NBTCompound {
  // Getters
  public String getString(String key);
  public int getInt(String key);
  // ...

  // Setters/Builders
  public NBTCompound setString(String key, String value);
  public NBTCompound setInt(String key, int value);
  // ...

  // Static constructors
  public static NBTCompound fromNMS(Object nmsNBTCompound);
  public static NBTCompound fromString(String nbtCompoundString);
}

This would look something like this:

NBTCompound functionArguments = new NBTCompound()
  .setInt("name1", 123)
  .setString("name2", "sometext1")
  .setNBTCompound("name3", new NBTCompound()
    .setInt("subname1", 456)
    .setString("subname2", "sometext2")
  );

Using Java's Maps

Or to save a lot of hassle, we could take inspiration from Rtag and use standard Java objects and primitive types. This would look something like this:

Map<String, Object> functionArguments = Map.of(
  "name1", 123,
  "name2", "sometext1",
  "name3", Map.of(
    "subname1", 456,
    "subname2", "sometext2"
  )
);

Usage

This can be used with the CommandAPI's existing FunctionWrapper.getFunction().execute() method:

FunctionWrapper.getFunction("my_namespace:my_function").execute(player, functionArguments);

Extra details

On an implementation note, we can call functions by instantiating them directly using the instantiate method which accepts the tag:

CommandFunction<CommandSourceStack> commandFunction = ...;
commandFunction.instantiate(tag, brigadierDispatcher, css);

We currently use this method here: https://github.com/JorelAli/CommandAPI/blob/c1399a18b63f88f3c4f3caccafadb78d423ce4d5/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.3/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R3.java#L331-L350

However, we don't call this in 1.20.2:

https://github.com/JorelAli/CommandAPI/blob/c1399a18b63f88f3c4f3caccafadb78d423ce4d5/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.2/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R2.java#L272-L282

For 1.20.2 compatibility, we can call the execute(CommandFunction, CommandSourceStack, TraceCallbacks, CompoundTag) method instead of having to instantiate the command explicitly