map-pinPlaceholder Integration

PhantomDungeons exposes its internal PlaceholderManager so that external plugins can register their own placeholder categories. Once registered, your placeholders resolve under the same %phantomdungeons_.._% expansion that is already installed on the server, no separate PAPI expansion needed.

circle-info

Requires PlaceholderAPI. If PlaceholderAPI is not installed, getPlaceholderManager() returns null. Always guard against this.


How It Works

Every placeholder that PhantomDungeons resolves goes through a list of PlaceholderCategory handlers. When a player triggers %phantomdungeons_my_value%, the manager walks the list and calls handle() on each category until one returns a non-null string. Your category simply needs to recognise identifiers that belong to it and return the right value.

%phantomdungeons_{identifier}%


  PlaceholderManager.onPlaceholderRequest()

          ├─ CurrencyPlaceholders.handle()  → null (not mine)
          ├─ ZonePlaceholders.handle()       → null (not mine)
          ├─ ...
          └─ YourCategory.handle()           → "42"  bingo

Quick Start

1

Step 1 - Implement PlaceholderCategory

import me.fergs.phantomdungeons.placeholders.PlaceholderCategory;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

public final class MyPlaceholders implements PlaceholderCategory {

    @Override
    public @NotNull String getPrefix() {
        return "myplugin"; // informational only — used for error logging
    }

    @Override
    public @Nullable String handle(@NotNull Player player, @NotNull String identifier) {
        // Only handle identifiers that belong to this category.
        // Return null to let the next category try.
        if (!identifier.startsWith("myplugin_")) {
            return null;
        }

        String suffix = identifier.substring("myplugin_".length());

        return switch (suffix) {
            case "score"  -> String.valueOf(getScore(player));
            case "rank"   -> getRank(player);
            case "status" -> isActive(player) ? "&aActive" : "&cInactive";
            default       -> null; // unknown sub-key
        };
    }
}
2

Step 2 - Register during onEnable()

import me.fergs.phantomdungeons.PhantomDungeons;
import me.fergs.phantomdungeons.placeholders.PlaceholderManager;

public final class MyPlugin extends JavaPlugin {

    @Override
    public void onEnable() {
        PhantomDungeons pd = (PhantomDungeons) getServer().getPluginManager().getPlugin("PhantomDungeons");
        if (pd == null) return;

        PlaceholderManager manager = pd.getPlaceholderManager();
        if (manager == null) return; // PlaceholderAPI not present

        manager.addCategory(new MyPlaceholders());
        getLogger().info("Registered placeholders under %phantomdungeons_myplugin_*%");
    }
}
3

Step 3 - Use in any PAPI-compatible field

# Menu lore, mob display names, boss bars, etc.
display: '&7Your score: &e%phantomdungeons_myplugin_score%'

PlaceholderCategory Reference

me.fergs.phantomdungeons.placeholders.PlaceholderCategory

Method
Required
Description

getPrefix()

Yes

A short label used in warning logs when your handle() throws. Does not filter identifiers automatically — you must check the prefix yourself inside handle().

handle(Player, String)

Yes

Called for every placeholder request. Return a non-null String to claim it, or null to pass it to the next category. The identifier is the full string after %phantomdungeons_ and before the closing % (bracket placeholders already resolved).

reload()

Not needed, up to you if you have your own configuration and values.

Called by PhantomDungeons when the server operator runs /dungeonsadmin reload. Override to flush any cached config values your category reads. No-op by default.


Identifier Conventions

PhantomDungeons built-in categories all follow the pattern {category}_{key} or {category}_{id}_{key}. Adopting the same convention keeps identifiers predictable.

Built-in pattern
Example

{category}_{key}

zone_current

{category}_{id}_{key}

currency_balance_money

{category}_{id}_{key}_{modifier}

enchant_critical_cost_10_formatted_abbrev

Prefix your identifiers with something unique to your plugin to avoid collisions with built-in or future categories:


Little Example - Some kind of kill counter

A complete, production-ready category that tracks player kills and supports a reload() hook.

Register it:

Usage:


Accessing PhantomDungeons APIs from Your Category

If your placeholders need to query PhantomDungeons data (economy, swords, zones, etc.) use the same lazy-getter pattern the built-in categories use, always null-check the manager before calling it, since modules may not be enabled.


Register before PlaceholderAPI resolves the first request

Register in onEnable(), after PhantomDungeons has loaded but before players log in. Declare the soft dependency in your plugin.yml:


Last updated