JorelAli / CommandAPI

A Bukkit/Spigot API for the command UI introduced in Minecraft 1.13

Home Page:https://commandapi.jorel.dev

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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

JorelAli opened this issue · comments

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:

// Converts NMS function to SimpleFunctionWrapper
private final SimpleFunctionWrapper convertFunction(CommandFunction<CommandSourceStack> commandFunction) {
ToIntFunction<CommandSourceStack> appliedObj = (CommandSourceStack css) -> runCommandFunction(commandFunction, css);
// Unpack the commands by instantiating the function with no CSS, then retrieving its entries
String[] commands = new String[0];
try {
final InstantiatedFunction<CommandSourceStack> instantiatedFunction = commandFunction.instantiate((CompoundTag) null, this.getBrigadierDispatcher(), null);
List<?> cArr = instantiatedFunction.entries();
commands = new String[cArr.size()];
for (int i = 0, size = cArr.size(); i < size; i++) {
commands[i] = cArr.get(i).toString();
}
} catch (FunctionInstantiationException functionInstantiationException) {
// We don't care if the function failed to instantiate
assert true;
}
return new SimpleFunctionWrapper(fromResourceLocation(commandFunction.id()), appliedObj, commands);
}

However, we don't call this in 1.20.2:

// Converts NMS function to SimpleFunctionWrapper
private final SimpleFunctionWrapper convertFunction(CommandFunction commandFunction) {
ToIntFunction<CommandSourceStack> appliedObj = (CommandSourceStack css) -> this.<MinecraftServer>getMinecraftServer().getFunctions().execute(commandFunction, css);
Entry[] cArr = commandFunction.getEntries();
String[] result = new String[cArr.length];
for (int i = 0, size = cArr.length; i < size; i++) {
result[i] = cArr[i].toString();
}
return new SimpleFunctionWrapper(fromResourceLocation(commandFunction.getId()), appliedObj, result);
}

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