Map arguments

A MapArgument can be used to provide a map of values. This argument uses an underlying GreedyStringArgument which means that this argument can only be used at the end of the argument list. It returns a LinkedHashMap object.


The MapArgumentBuilder

Similar to the ListArgument, this argument also uses a builder class to construct it.

\begin{align} &\quad\texttt{Create a MapArgumentBuilder and possibly provide the delimiter} \\ \rightarrow&\quad\texttt{Provide the mapper from a string to an object of the provided key type} \\ \rightarrow&\quad\texttt{Provide the mapper from a string to an object of the provided value type} \\ \rightarrow&\quad\texttt{Provide the list with keys to pull suggestions from} \\ \rightarrow&\quad\texttt{Provide the list with values to pull suggestions from} \\ \rightarrow&\quad\texttt{Build the MapArgument} \end{align}

Building a MapArgument

To start building the argument, you first have to construct a MapArgumentBuilder parameterized over the types the key and the value are supposed to have. If you wanted to construct a MapArgument that returns a LinkedHashMap<String, Integer> you would construct the MapArgumentBuilder like this:

new MapArgumentBuilder<String, Integer>

The MapArgumentBuilder has two possible constructors:

public MapArgumentBuilder<K, V>(String nodeName);
public MapArgumentBuilder<K, V>(String nodeName, char delimiter);
  • nodeName: This parameter determines the node name of the MapArgument
  • delimiter: This parameter determines the delimiter. This separates a key from a value. When not provided, it defaults to a colon (:)

$$\downarrow$$

Providing mapper functions

The mapper functions are used to parse the argument when entered. Because a GreedyStringArgument returns a String, we need a way to convert a String into an object specified by the type parameters.

When providing mappers, you first need to provide the key mapper:

public MapArgumentBuilder withKeyMapper(Function<String, K>);

You then have to provide the value mapper:

public MapArgumentBuilder withValueMapper(Function<String, V>);

$$\downarrow$$

Providing suggestions

When providing suggestions you have the choice whether players are allowed to enter any key/value pair or only key/value pairs specified by the MapArgument. To accomplish this the MapArgumentBuilder provides different methods.

Similar to the mappers, you first have to provide the key suggestions:

public MapArgumentBuilder withKeyList(List<String> keyList);
public MapArgumentBuilder withoutKeyList();

Next, you have to provide the value suggestions. In addition to the two possibilities presented for the key suggestions, here you also have the possibility to define whether a value can be written multiple times.

public MapArgumentBuilder withValueList(List<String> valueList);

public MapArgumentBuilder withValueList(List<String> valueList, boolean allowValueDuplicates);
public MapArgumentBuilder withoutValueList();

If you choose to allow a value to be written multiple times you have to set allowValueDuplicates to true. By default, it is set to false and does not allow values to be written multiple times.

$$\downarrow$$

Building the MapArgument

To finish building the MapArgument, you have to call the build() method. This will return a new MapArgument object.

public MapArgument<K, V> build();

Examples

Example - /sendmessage command

Let's say we want to create a command that we can execute to send multiple players messages without typing the command more than once. For that, we create a command with the following syntax:

/sendmessage <message>

To implement that, we create a command that uses a MapArgument and use Player objects as keys and String objects as values:

new CommandAPICommand("sendmessage")
    // Parameter 'delimiter' is missing, delimiter will be a colon
    .withArguments(new MapArgumentBuilder<Player, String>("message")

        // Providing a key mapper to convert a String into a Player
        .withKeyMapper(Bukkit::getPlayer)

        // Providing a value mapper to leave the message how it was sent
        .withValueMapper(s -> s)

        // Providing a list of player names to be used as keys
        .withKeyList(Bukkit.getOnlinePlayers().stream().map(Player::getName).toList())

        // Don't provide a list of values so messages can be chosen without restrictions
        .withoutValueList()

        // Build the MapArgument
        .build()
    )
    .executesPlayer((player, args) -> {
        // The MapArgument returns a LinkedHashMap
        LinkedHashMap<Player, String> map = (LinkedHashMap<Player, String>) args.get("message");

        // Sending the messages to the players
        for (Player messageRecipient : map.keySet()) {
            messageRecipient.sendMessage(map.get(messageRecipient));
        }
    })
    .register();
CommandAPICommand("sendmessage")
    // Parameter 'delimiter' is missing, delimiter will be a colon
    .withArguments(MapArgumentBuilder<Player, String>("message")

        // Providing a key mapper to convert a String into a Player
        .withKeyMapper { s: String -> Bukkit.getPlayer(s) }

        // Providing a value mapper to leave the message how it was sent
        .withValueMapper { s: String -> s }

        // Providing a list of player names to be used as keys
        .withKeyList(Bukkit.getOnlinePlayers().map { player: Player -> player.name }.toList())

        // Don't provide a list of values so messages can be chosen without restrictions
        .withoutValueList()

        // Build the MapArgument
        .build()
    )
    .executesPlayer(PlayerCommandExecutor { _, args ->
        // The MapArgument returns a LinkedHashMap
        val map: LinkedHashMap<Player, String> = args["message"] as LinkedHashMap<Player, String>

        // Sending the messages to the players
        for (messageRecipient in map.keys) {
            messageRecipient.sendMessage(map[messageRecipient]!!)
        }
    })
    .register()
commandAPICommand("sendmessage") {
    // Parameter 'delimiter' is missing, delimiter will be a colon
    argument(MapArgumentBuilder<Player, String>("message")

        // Providing a key mapper to convert a String into a Player
        .withKeyMapper { s: String -> Bukkit.getPlayer(s) }

        // Providing a value mapper to leave the message how it was sent
        .withValueMapper { s: String -> s }

        // Providing a list of player names to be used as keys
        .withKeyList(Bukkit.getOnlinePlayers().map { player: Player -> player.name }.toList())

        // Don't provide a list of values so messages can be chosen without restrictions
        .withoutValueList()

        // Build the MapArgument
        .build()
    )
    playerExecutor { _, args ->
        // The MapArgument returns a LinkedHashMap
        val map: LinkedHashMap<Player, String> = args["message"] as LinkedHashMap<Player, String>

        // Sending the messages to the players
        for (messageRecipient in map.keys) {
            messageRecipient.sendMessage(map[messageRecipient]!!)
        }
    }
}

Developer's Note:

The MapArgument is very strict and doesn't have room for any errors. The syntax for key/value pairs of the MapArgument is as following:

<key><delimiter>"<value>"

Let's say you are on a server with two players, Player1 and Player2. We want to send both of them the message Hello, <playerName>! To do that, we use the previously declared sendmessage command like this:

/sendmessage Player1:"Hello, Player1!" Player2:"Hello, Player2!"

Here we use a colon as the delimiter because we didn't specify a delimiter in the MapArgumentBuilder constructor.