Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
JorelAli opened this issue Feb 26, 2024 · 0 comments
Labels
enhancement New feature or request

Comments

@JorelAli
Copy link
Owner

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

@JorelAli JorelAli added the enhancement New feature or request label Feb 26, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

1 participant