PlaceholderAPI/PlaceholderExpansion.md
2023-03-27 17:55:02 +02:00

16 KiB

Overview

This page will cover how you can create your own PlaceholderExpansion which you can either Upload to the eCloud or integrate into your own plugin.

It's worth noting that PlaceholderAPI relies on expansions being installed. PlaceholderAPI only acts as the core replacing utility while the expansions allow other plugins to use any installed placeholder in their own messages. You can download Expansions either directly from the eCloud yourself, or download them through the download command of PlaceholderAPI.

Table of Contents

Getting started

For starters, you need to decide what type of PlaceholderExpansion you want to create. There are various ways to create an expansion. This page will cover the most common ones.

Common Parts

All shown examples will share the same common parts that belong to the PlaceholderExpansion class.
In order to not repeat the same basic info for each method throughout this page, and to greatly reduce its overall length, we will cover the most basic/necessary ones here.

Basic PlaceholderExpansion Structure

package at.helpch.placeholderapi.example.expansions;

import org.bukkit.OfflinePlayer;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;

public class SomeExpansion extends PlaceholderExpansion {

    @Override
    public String getAuthor() {
        return "someauthor";
    }
    
    @Override
    public String getIdentifier() {
        return "example";
    }

    @Override
    public String getVersion() {
        return "1.0.0";
    }
}

Let's quickly break down the different methods you have to implement.

  • getAuthor

    This method allows you to set the name of the expansion's author.

  • getIdentifier

    The identifier is the part in the placeholder that is between the first % (Or { if bracket placeholders are used) and the first _.
    Because of that can you not use %, {, } or _ in your identifier.

    If you still want to use those symbols can you override the getName() method to display a different name.

  • getVersion

    This is a string, which means it can contain more than just a number. This is used to determine if a new update is available or not when the expansion is shared on the eCloud. For expansions that are part of a plugin, this does not really matter.

Those are all the neccessary parts for your PlaceholderExpansion.
Any other methods that are part of the PlaceholderExpansion class are optional and will usually not be used, or will default to a specific value. Please read the Javadoc comments of those methods for more information.

You must choose between one of these two methods for handling the actual parsing of placeholders (Unless you're using Relational Placeholders):

  • onRequest(OfflinePlayer, String)

    If not explicitly set, this will automatically call onPlaceholderRequest(Player, String). This method is recommended as it allows the usage of offline players, meaning the player doesn't need to be online to obtain certain data from them like name or UUID.
  • onPlaceholderRequest(Player, String)

    If not set, this method will return null which PlaceholderAPI sees as an invalid placeholder.
    The Player can be null, so keep that in mind when handling your placeholders.

PlaceholderAPI will always call onRequest(OfflinePlayer, String) in an expansion.


Making an internal Expansion

Internal PlaceholderExpansions are classes directly implemented into the plugin they depend on. The main benefit is, that you're not required to do any canRegister() check for your own plugin, as it is loaded within it.
Another benefit is that you can more easily access plugin data using Dependency Injection should an API not be accessible for the plugin.

It is important to note that PlaceholderExpansions loaded manually through the register() method (Therefore not being loaded by PlaceholderAPI itself) require to have the persist() method return true to avoid the expansion being unloaded during a /papi reload.

Depending on what info you try to access from your plugin (i.e. through the main class or a dedicated API) can you use Dependency Injection to obtain an instance of whatever you need.
Keep in mind that as mentioned earlier, the Expansion class won't be reloaded when persist() is set to true, so you have to refresh any info that may get outdated during a reload yourself.

Here is an example of a possible Dependency Injection:

package at.helpch.placeholderapi.example.expansion;

import at.helpch.placeholderapi.example.SomePlugin;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;

public class SomeExpansion extends PlaceholderExpansion {
    
    private final SomePlugin plugin;
    
    public SomeExpansion(SomePlugin plugin) {
        this.plugin = plugin;
    }
    
    // Imported methods from PlaceholderExpansion
}

Full Example

Please read the Common Parts Section for details on all the methods.

Below is a full example of an internal Expansion class. Please note the override of the persist() method to guarantee the Expansion isn't unloaded during a /papi reload operation.
We also use the provided SomePlugin instance for information such as the version and authors. This allows us to keep the code clean while not having to deal with updating the expansion's information every time we make changes to the plugin.

package at.helpch.placeholderapi.example.expansion;

import at.helpch.placeholderapi.example.SomePlugin;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import org.bukkit.OfflinePlayer;

public class SomeExpansion extends PlaceholderExpansion {
    
    private final SomePlugin plugin;
    
    public SomeExpansion(SomePlugin plugin) {
        this.plugin = plugin;
    }
    
    @Override
    public String getAuthor() {
        return String.join(", ", plugin.getDescription().getAuthors());
    }
    
    @Override
    public String getIdentifier() {
        return "example";
    }
    
    @Override
    public String getVersion() {
        return plugin.getDescription().getVersion();
    }
    
    // This override is required or PlaceholderAPI will unregister your expansion on /papi reload
    @Override
    public boolean persist() {
        return true;
    }
    
    @Override
    public String onRequest(OfflinePlayer player, String params) {
        if (params.equalsIgnoreCase("placeholder1")) {
            return plugin.getConfig().getString("placeholders.placeholder1", "default1");
        }
        
        if (params.equalsIgnoreCase("placeholder2")) {
            return plugin.getConfig().getString("placeholders.placeholder2", "default2");
        }
        
        return null; // Unknown Placeholder provided
    }
}

Register your expansion

The main downside of an internal expansion is, that it can't be loaded automatically by PlaceholderAPI and instead requires you to manually register it.
To do that, create a new instance of your Expansion class and call the register() method of it.

Below is an example of loading the Expansion in the Plugin's main class inside the onEnable() method.

package at.helpch.placeholderapi.example;

import at.helpch.placeholderapi.example.expansion.SomeExpansion;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;

public class SomePlugin extends JavaPlugin {
    
    @Override
    public void onEnable() {
        // Make sure PlaceholderAPI is installed and enabled
        if (Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")) {
            // Register the expansion
            new SomeExpansion(this).register();
        }
    }
}

Making an external Expansion

External expansions are separate Jar files containing the PlaceholderExpansion extending class.
They are recommended for the following scenarios:

  • Your Expansion doesn't depend on any Plugin.
  • Your Expansion depends on a Plugin AND you can't directly implement the Class into it.

For Expansions made for a plugin is it recommended to try and use internal Expansions. If you can't implement it yourself, i.e. because you're not the dev of it and it isn't open source, should you consider alternative solutions (i.e. asking the dev to implement it) before using this method.

Benefits of this type of expansion are 1) automatic loading through PlaceholderAPI by adding them to your expansions folder and 2) having the option to upload them on the eCloud, allowing it to be downloaded through [[/papi ecloud download <expansion>|Commands#papi-ecloud-download]] automatically (After it has been verified).

Downsides can be a more tedious setup to make sure any required plugin/dependency is loaded before registering the Expansion.

Full Example (Without Dependency)

Please read the Common Parts Section for details on all the methods.

Below is a full example of an external PlaceholderExpansion without any dependencies such as plugins.

package at.helpch.placeholderapi.example.expansion;

import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import org.bukkit.OfflinePlayer;

public class SomeExpansion extends PlaceholderExpansion {
    
    @Override
    public String getAuthor() {
        return "SomeAuthor";
    }
    
    @Override
    public String getIdentifier() {
        return "example";
    }
    
    @Override
    public String getVersion() {
        return "1.0.0";
    }
    
    @Override
    public String onRequest(OfflinePlayer player, String params) {
        if (params.equalsIgnoreCase("player_name")) {
            // player_name requires a valid OfflinePlayer
            return player == null ? null : player.getName();
        }
        
        if (params.equalsIgnoreCase("placeholder1")) {
            return "Placeholder Text 1";
        }
        
        if (params.equalsIgnoreCase("placeholder2")) {
            return "Placeholder Text 2";
        }
        
        return null; // Unknown Placeholder provided
    }
}

Full example (With Dependency)

Please read the Common Parts Section for details on all the methods.

The below example shows a possible setup of an external PlaceholderExpansion that depends on a Plugin to be present.

package at.helpch.placeholderapi.example.expansion;

import at.helpch.placeholderapi.example.SomePlugin;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import org.bukkit.OfflinePlayer;

public class SomeExpansion extends PlaceholderExpansion {
    
    // This instance is assigned in canRegister() and can therefore not be final
    private SomePlugin plugin;
    
    @Override
    public String getAuthor() {
        return "SomeAuthor";
    }
    
    @Override
    public String getIdentifier() {
        return "example";
    }
    
    @Override
    public String getVersion() {
        return "1.0.0";
    }
    
    // Allows us to define a plugin the expansion depends on
    @Override
    public String getRequiredPlugin() {
        return "SomePlugin";
    }
    
    /*
     * This method needs to be overriden if your Expansion requires
     * a plugin or other dependency to be present to work.
     *
     * Returning false will cancel the Expansion registration.
     */
    @Override
    public boolean canRegister() {
        /*
         * We do 2 things here:
         *   1. Check if the required plugin is present and enabled
         *   2. Obtain an instance of the plugin to use
         */
        return (plugin = (SomePlugin) Bukkit.getPluginManager().getPlugin(getRequiredPlugin())) != null;
    }
        
    
    @Override
    public String onRequest(OfflinePlayer player, String params) {
        if (params.equalsIgnoreCase("placeholder1")) {
            return plugin.getConfig().getString("placeholders.placeholder1", "default1");
        }
        
        if (params.equalsIgnoreCase("placeholder2")) {
            return plugin.getConfig().getString("placeholders.placeholder2", "default2");
        }
        
        return null; // Unknown Placeholder provided
    }
}

Relational Placeholders

Relational Placeholders are a special kind of placeholder that allow the usage of 2 Players for whatever you like to do. The most common use is to evaluate their relation to each other (i.e. check if Player one is in the same team as Player two) and return an output based on that.

Quick Notes

Relational Placeholders are always prefixed with rel_, meaning that a relational placeholder called example with value friend looks like %rel_example_friend% when used.

Adding Relational Placeholders

To add relational placeholders will you need to implement the Relational interface into your Expansion class and override the onPlaceholderRequest(Player, Player, String) method.

Full Example

Please read the Common Parts Section for details on all the methods.

Below is a full example of using Relational Placeholders.
For the sake of simplicity are we using parts of the internal Expansion Example here and assume that the SomePlugin class offers a areFriends(Player, Player) method that returns a boolean value.

package at.helpch.placeholderapi.example.expansions;

import at.helpch.placeholderapi.example.SomePlugin;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import me.clip.placeholderapi.expansion.Relational;
import org.bukkit.ChatColor;
import org.bukkit.Player;

public class SomeExpansion extends PlaceholderExpansion implements Relational {

    private final SomePlugin plugin;
    
    public SomeExpansion(SomePlugin plugin) {
        this.plugin = plugin;
    }
    
    @Override
    public String getAuthor() {
        return String.join(", ", plugin.getDescription().getAuthors());
    }
    
    @Override
    public String getIdentifier() {
        return "example";
    }
    
    @Override
    public String getVersion() {
        return plugin.getDescription().getVersion();
    }
    
    // This override is required or PlaceholderAPI will unregister your expansion on /papi reload
    @Override
    public boolean persist() {
        return true;
    }
    
    @Override
    public String onPlaceholderRequest(Player one, Player two, String identifier) {
        if(one == null || two == null)
            return null; // We require both Players to be online
            
        if(params.equalsIgnoreCase("friend")) {
            if(plugin.areFriends(one, two)) {
                return ChatColor.GREEN + one.getName() + " and " + two.getName() + " are friends!";
            } else {
                return ChatColor.GREEN + one.getName() + " and " + two.getName() + " are not friends!";
            }
        }
        
        return null; // Placeholder is unknown by the Expansion
    }
}