This commit is contained in:
PiggyPiglet
2026-02-20 21:06:02 +08:00
14 changed files with 190 additions and 161 deletions

View File

@@ -7,7 +7,7 @@ plugins {
}
group = "at.helpch"
version = "1.0.7-DEV-${System.getProperty("BUILD_NUMBER")}-CurseForge"
version = "1.0.7-CurseForge"
description = "An awesome placeholder provider!"
@@ -15,15 +15,13 @@ repositories {
mavenCentral()
mavenLocal()
maven {
url = uri("https://repo.codemc.io/repository/hytale/")
}
maven("https://maven.hytale.com/release/")
}
dependencies {
implementation("org.yaml:snakeyaml:2.5")
compileOnly("com.hypixel.hytale:Server:2026.01.17-4b0f30090")
compileOnly("com.hypixel.hytale:Server:latest.release")
compileOnlyApi("org.jetbrains:annotations:23.0.0")
}

View File

@@ -2,6 +2,7 @@ package at.helpch.placeholderapi;
import at.helpch.placeholderapi.commands.PlaceholderCommandRouter;
import at.helpch.placeholderapi.configuration.ConfigManager;
import at.helpch.placeholderapi.expansion.PlaceholderExpansion;
import at.helpch.placeholderapi.expansion.manager.CloudExpansionManager;
import at.helpch.placeholderapi.expansion.manager.LocalExpansionManager;
import at.helpch.placeholderapi.metrics.MetricsManager;
@@ -78,6 +79,12 @@ public class PlaceholderAPIPlugin extends JavaPlugin {
configManager.setup();
localExpansionManager.load(sender);
for (final PlaceholderExpansion expansion : localExpansionManager.getExpansions()) {
if (expansion.getExpansionType() == PlaceholderExpansion.Type.INTERNAL) {
// when the config gets reloaded, getConfig for internal expansions will return a map instead of the correct object
localExpansionManager.createConfig(expansion);
}
}
if (configManager.config().cloudEnabled()) {
cloudExpansionManager.load();

View File

@@ -101,7 +101,7 @@ public final class PlaceholderCommandRouter extends AbstractCommand {
final PlaceholderCommand target = commands.get(search);
if (target == null) {
sender.sendMessage(Message.raw("Unknown command ").color(Color.RED).insert(Message.raw(search).color(Color.GRAY)));
sender.sendMessage(Message.raw("Unknown command ").color(Color.RED).insert(Message.raw(search).color(Color.LIGHT_GRAY)));
return CompletableFuture.completedFuture(null);
}

View File

@@ -78,9 +78,9 @@ public final class CommandECloud extends PlaceholderCommand {
if (params.isEmpty()) {
Message message = Message.empty()
.insert(Message.raw("PlaceholderAPI ").color(Color.CYAN).bold(true))
.insert(Message.raw("- ").color(Color.DARK_GRAY))
.insert(Message.raw("eCloud Help Menu ").color(Color.GRAY))
.insert(Message.raw("-\n").color(Color.DARK_GRAY));
.insert(Message.raw("- ").color(Color.GRAY))
.insert(Message.raw("eCloud Help Menu ").color(Color.LIGHT_GRAY))
.insert(Message.raw("-\n").color(Color.GRAY));
final List<String[]> commands = List.of(
new String[]{"ecloud status", "View status of the eCloud"},
@@ -96,7 +96,7 @@ public final class CommandECloud extends PlaceholderCommand {
for (String[] command : commands) {
message = message.insert(Message.raw("\n/papi ").color(Color.CYAN))
.insert(Message.raw(command[0]).color(Color.WHITE))
.insert(Message.raw("\n " + command[1]).color(Color.GRAY));
.insert(Message.raw("\n " + command[1]).color(Color.LIGHT_GRAY));
}
sender.sendMessage(message);
@@ -128,7 +128,7 @@ public final class CommandECloud extends PlaceholderCommand {
final PlaceholderCommand target = commands.get(search);
if (target == null) {
sender.sendMessage(Message.raw("Unknown command ").color(Color.RED).insert(Message.raw("ecloud " + search).color(Color.GRAY)));
sender.sendMessage(Message.raw("Unknown command ").color(Color.RED).insert(Message.raw("ecloud " + search).color(Color.LIGHT_GRAY)));
// Msg.msg(sender, "&cUnknown command &7ecloud " + search);
return;
}

View File

@@ -39,15 +39,14 @@ public final class CommandECloudExpansionList extends PlaceholderCommand {
private static final int PAGE_SIZE = 10;
@NotNull
private static final Function<CloudExpansion, String> EXPANSION_LATEST_VERSION =
@NotNull
private static final Function<CloudExpansion, String> EXPANSION_LATEST_VERSION =
CloudExpansion::getLatestVersion;
@NotNull
private static final Function<CloudExpansion, String> EXPANSION_CURRENT_VERSION =
@NotNull
private static final Function<CloudExpansion, String> EXPANSION_CURRENT_VERSION =
expansion -> PlaceholderAPIPlugin.instance().localExpansionManager()
.findExpansionByName(expansion.getName()).map(PlaceholderExpansion::getVersion)
.orElse("Unknown");
.findExpansionByName(expansion.getName()).map(PlaceholderExpansion::getVersion)
.orElse("Unknown");
@Unmodifiable
private static final Set<String> OPTIONS = Set.of("all", "installed");
@@ -105,7 +104,7 @@ public final class CommandECloudExpansionList extends PlaceholderCommand {
return title
.insert(Message.raw(" Page").color(Color.CYAN))
.insert(Message.raw(": ").color(Color.GRAY))
.insert(Message.raw(": ").color(Color.LIGHT_GRAY))
.insert(Message.raw(String.valueOf(page)).color(Color.GREEN));
}
@@ -118,7 +117,7 @@ public final class CommandECloudExpansionList extends PlaceholderCommand {
Message line = Message.empty();
final int expansionNumber = index + ((page - 1) * PAGE_SIZE) + 1;
line = line.insert(Message.raw(expansionNumber + ". ").color(Color.DARK_GRAY));
line = line.insert(Message.raw(expansionNumber + ". ").color(Color.GRAY));
final Color expansionColour;
@@ -128,7 +127,7 @@ public final class CommandECloudExpansionList extends PlaceholderCommand {
if (expansion.hasExpansion()) {
expansionColour = Color.GREEN;
} else {
expansionColour = Color.GRAY;
expansionColour = Color.LIGHT_GRAY;
}
}
@@ -175,7 +174,17 @@ public final class CommandECloudExpansionList extends PlaceholderCommand {
// right.clickEvent(ClickEvent.runCommand("/papi ecloud list " + target + " " + (page + 1)));
// }
message = message.insert(Message.raw(" - " + page + " of " + limit + " - ").color(Color.GREEN));
message = message
.insert(Message.raw(" - ").color(Color.LIGHT_GRAY))
.insert(Message.raw(page + " of " + limit).color(Color.GREEN))
.insert(Message.raw(" - ").color(Color.LIGHT_GRAY))
.insert(Message.raw("[").color(Color.GRAY))
.insert(Message.raw("Available").color(Color.LIGHT_GRAY))
.insert(Message.raw(", ").color(Color.GRAY))
.insert(Message.raw("Installed").color(Color.GREEN))
.insert(Message.raw(", ").color(Color.GRAY))
.insert(Message.raw("Update Available").color(Color.YELLOW))
.insert(Message.raw("]").color(Color.GRAY));
}
return message;
@@ -220,7 +229,7 @@ public final class CommandECloudExpansionList extends PlaceholderCommand {
message = message.insert(Message.raw("\n"));
final int separatorLength = Arrays.stream(widths).sum() + (columnCount * 2);
message = message.insert(Message.raw("-".repeat(separatorLength)).color(Color.DARK_GRAY));
message = message.insert(Message.raw("-".repeat(separatorLength)).color(Color.GRAY));
if (rows.size() > 1) {
message = message.insert(Message.raw("\n"));
@@ -232,10 +241,10 @@ public final class CommandECloudExpansionList extends PlaceholderCommand {
final Color nameColor = expansion.shouldUpdate()
? Color.YELLOW
: (expansion.hasExpansion() ? Color.GREEN : Color.GRAY);
: (expansion.hasExpansion() ? Color.GREEN : Color.LIGHT_GRAY);
final List<Color> rowColors = List.of(
Color.DARK_GRAY,
Color.GRAY,
nameColor,
Color.WHITE,
expansion.getVersion().isVerified() ? Color.GREEN : Color.RED,

View File

@@ -75,7 +75,7 @@ public final class CommandECloudExpansionPlaceholders extends PlaceholderCommand
Message message = Message.raw(" ").color(Color.ORANGE)
.insert(Message.raw(String.valueOf(placeholders.size())).color(Color.ORANGE))
.insert(Message.raw(" placeholders: ").color(Color.GRAY));
.insert(Message.raw(" placeholders: ").color(Color.LIGHT_GRAY));
for (int i = 0; i < partitions.size(); i++) {
if (i == 0) {
@@ -86,7 +86,7 @@ public final class CommandECloudExpansionPlaceholders extends PlaceholderCommand
for (int j = 0; j < partition.size(); j++) {
message = message.insert(Message.raw(partition.get(j)).color(Color.GREEN));
if (j < partition.size() - 1) {
message = message.insert(Message.raw(", ").color(Color.GRAY));
message = message.insert(Message.raw(", ").color(Color.LIGHT_GRAY));
}
}

View File

@@ -51,9 +51,9 @@ public final class CommandECloudStatus extends PlaceholderCommand {
Message message = Message.raw("There are ").color(Color.CYAN)
.insert(Message.raw(String.valueOf(expansionCount)).color(Color.GREEN))
.insert(Message.raw(" expansions available on the eCloud.\n").color(Color.CYAN))
.insert(Message.raw("A total of ").color(Color.GRAY))
.insert(Message.raw("A total of ").color(Color.LIGHT_GRAY))
.insert(Message.raw(String.valueOf(authorCount)).color(Color.WHITE))
.insert(Message.raw(" authors have contributed Hytale expansions to the eCloud.\n").color(Color.GRAY));
.insert(Message.raw(" authors have contributed Hytale expansions to the eCloud.\n").color(Color.LIGHT_GRAY));
if (updateCount > 0) {
message = message

View File

@@ -48,12 +48,12 @@ public final class CommandHelp extends PlaceholderCommand {
// final PluginDescriptionFile description = plugin.getDescription();
final Message message = Message.raw("PlaceholderAPI ").color(Color.CYAN).bold(true)
.insert(Message.raw("- ").color(Color.DARK_GRAY).bold(false))
.insert(Message.raw("- ").color(Color.GRAY).bold(false))
.insert(Message.raw("Help Menu ").color(Color.WHITE).bold(false))
.insert(Message.raw("- ").color(Color.DARK_GRAY).bold(false))
.insert(Message.raw("(").color(Color.GRAY).bold(false))
.insert(Message.raw("- ").color(Color.GRAY).bold(false))
.insert(Message.raw("(").color(Color.LIGHT_GRAY).bold(false))
.insert(Message.raw(description.getVersion().toString()).color(Color.WHITE).bold(false))
.insert(Message.raw(")").color(Color.GRAY).bold(false))
.insert(Message.raw(")").color(Color.LIGHT_GRAY).bold(false))
.insert(Message.raw("\n"))
.insert(genCommandMsg("bcparse", "<me|--null|player name> <message>", "Parse a message with placeholders and broadcast it"))
.insert(genCommandMsg("cmdparse", "<me|player> <command with placeholders>", "Parse a message with relational placeholders"))
@@ -107,6 +107,6 @@ public final class CommandHelp extends PlaceholderCommand {
}
return message
.insert(Message.raw("\n " + description).color(Color.gray).bold(false));
.insert(Message.raw("\n " + description).color(Color.LIGHT_GRAY).bold(false));
}
}

View File

@@ -59,15 +59,15 @@ public final class CommandInfo extends PlaceholderCommand {
}
Message message = Message.empty()
.insert(Message.raw("Placeholder expansion info for:").color(Color.GRAY))
.insert(Message.raw("Placeholder expansion info for:").color(Color.LIGHT_GRAY))
.insert(Message.raw(expansion.getName() + "\n").color(Color.WHITE))
.insert(Message.raw("Status: ").color(Color.GRAY))
.insert(Message.raw("Status: ").color(Color.LIGHT_GRAY))
.insert(Message.raw(expansion.isRegistered() ? "Registered" : "Not Registered").color(expansion.isRegistered() ? Color.GREEN : Color.RED))
.insert("\n");
final String author = expansion.getAuthor();
if (author != null) {
message = message.insert(Message.raw("Author: ").color(Color.GRAY))
message = message.insert(Message.raw("Author: ").color(Color.LIGHT_GRAY))
.insert(Message.raw(author + "\n").color(Color.WHITE));
// builder.append("&7Author: &r")
// .append(author)
@@ -76,7 +76,7 @@ public final class CommandInfo extends PlaceholderCommand {
final String version = expansion.getVersion();
if (version != null) {
message = message.insert(Message.raw("Version: ").color(Color.GRAY))
message = message.insert(Message.raw("Version: ").color(Color.LIGHT_GRAY))
.insert(Message.raw(version + "\n").color(Color.WHITE));
// builder.append("&7Version: &r")
@@ -86,7 +86,7 @@ public final class CommandInfo extends PlaceholderCommand {
final String requiredPlugin = expansion.getRequiredPlugin();
if (requiredPlugin != null) {
message = message.insert(Message.raw("Requires plugin: ").color(Color.GRAY))
message = message.insert(Message.raw("Requires plugin: ").color(Color.LIGHT_GRAY))
.insert(Message.raw(requiredPlugin + '\n').color(Color.WHITE));
// builder.append("&7Requires plugin: &r")
@@ -96,9 +96,9 @@ public final class CommandInfo extends PlaceholderCommand {
final List<String> placeholders = expansion.getPlaceholders();
if (placeholders != null && !placeholders.isEmpty()) {
message = message.insert(Message.raw("-- ").color(Color.DARK_GRAY))
.insert(Message.raw("Placeholders ").color(Color.GRAY))
.insert(Message.raw("--\n").color(Color.DARK_GRAY));
message = message.insert(Message.raw("-- ").color(Color.GRAY))
.insert(Message.raw("Placeholders ").color(Color.LIGHT_GRAY))
.insert(Message.raw("--\n").color(Color.GRAY));
// builder.append("&8&m-- &7Placeholders &8&m--&r")
// .append('\n');

View File

@@ -58,9 +58,9 @@ public final class CommandList extends PlaceholderCommand {
// final List<List<String>> partitions = Lists
// .partition(identifiers.stream().sorted().collect(Collectors.toList()), 10);
Message message = Message.raw("A total of ").color(Color.GRAY)
Message message = Message.raw("A total of ").color(Color.LIGHT_GRAY)
.insert(Message.raw(identifiers.size() + " ").color(Color.WHITE))
.insert(Message.raw("placeholder hook(s) are active: ").color(Color.GRAY));
.insert(Message.raw("placeholder hook(s) are active: ").color(Color.LIGHT_GRAY));
for (int i = 0; i < partitions.size(); ++i) {
final List<String> partition = partitions.get(i);
@@ -69,7 +69,7 @@ public final class CommandList extends PlaceholderCommand {
message = message.insert(Message.raw(partition.get(j)).color(Color.GREEN));
if (j != partition.size() - 1) {
message = message.insert(Message.raw(", ").color(Color.GRAY));
message = message.insert(Message.raw(", ").color(Color.LIGHT_GRAY));
}
}

View File

@@ -29,13 +29,17 @@ import at.helpch.placeholderapi.PlaceholderAPI;
import at.helpch.placeholderapi.PlaceholderAPIPlugin;
import at.helpch.placeholderapi.commands.PlaceholderCommand;
import at.helpch.placeholderapi.expansion.PlaceholderExpansion;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.server.core.Message;
import com.hypixel.hytale.server.core.NameMatching;
import com.hypixel.hytale.server.core.command.system.CommandSender;
import com.hypixel.hytale.server.core.entity.Entity;
import com.hypixel.hytale.server.core.entity.entities.Player;
import com.hypixel.hytale.server.core.universe.PlayerRef;
import com.hypixel.hytale.server.core.universe.Universe;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Unmodifiable;
@@ -94,7 +98,7 @@ public final class CommandParse extends PlaceholderCommand {
sender.sendMessage(Message.raw("You must provide a target and a message: ").color(Color.RED)
.insert(Message.raw("/papi ").color(Color.CYAN))
.insert(Message.raw(command ? "cmdparse" : (broadcast ? "bcparse" : "parse")).color(Color.CYAN))
.insert(Message.raw(" {target}").color(Color.GRAY))
.insert(Message.raw(" {target}").color(Color.LIGHT_GRAY))
.insert(Message.raw(" {message}").color(Color.GREEN)));
return;
}
@@ -103,12 +107,12 @@ public final class CommandParse extends PlaceholderCommand {
if ("me".equalsIgnoreCase(params.getFirst())) {
if (!(sender instanceof Player) && !(sender instanceof PlayerRef)) {
sender.sendMessage(Message.raw("You must be a player to use ").color(Color.RED).insert(Message.raw("me").color(Color.GRAY)).insert(Message.raw(" as a target!").color(Color.RED)));
sender.sendMessage(Message.raw("You must be a player to use ").color(Color.RED).insert(Message.raw("me").color(Color.LIGHT_GRAY)).insert(Message.raw(" as a target!").color(Color.RED)));
return;
}
if (sender instanceof Player) {
player = ((Player) sender).getPlayerRef();
player = getPlayerRef((Player) sender);
} else {
player = (PlayerRef) sender;
}
@@ -144,7 +148,7 @@ public final class CommandParse extends PlaceholderCommand {
if (params.size() < 3) {
sender.sendMessage(Message.raw("You must supply two targets, and a message: ").color(Color.RED)
.insert(Message.raw("/papi parserel ").color(Color.CYAN))
.insert(Message.raw("{target one} {target two} ").color(Color.GRAY))
.insert(Message.raw("{target one} {target two} ").color(Color.LIGHT_GRAY))
.insert(Message.raw("{message}").color(Color.GREEN)));
return;
}
@@ -154,13 +158,13 @@ public final class CommandParse extends PlaceholderCommand {
if ("me".equalsIgnoreCase(params.get(0))) {
if (!(sender instanceof Player) && !(sender instanceof PlayerRef)) {
sender.sendMessage(Message.raw("You must be a player to use ").color(Color.RED)
.insert(Message.raw("me").color(Color.GRAY))
.insert(Message.raw("me").color(Color.LIGHT_GRAY))
.insert(Message.raw(" as a target!").color(Color.RED)));
return;
}
if (sender instanceof Player) {
playerOne = ((Player) sender).getPlayerRef();
playerOne = getPlayerRef((Player) sender);
} else {
playerOne = (PlayerRef) sender;
}
@@ -177,12 +181,12 @@ public final class CommandParse extends PlaceholderCommand {
if ("me".equalsIgnoreCase(params.get(1))) {
if (!(sender instanceof Player) && !(sender instanceof PlayerRef)) {
sender.sendMessage(Message.raw("You must be a player to use ").color(Color.RED).insert(Message.raw("me").color(Color.GRAY)).insert(Message.raw(" as a target!").color(Color.RED)));
sender.sendMessage(Message.raw("You must be a player to use ").color(Color.RED).insert(Message.raw("me").color(Color.LIGHT_GRAY)).insert(Message.raw(" as a target!").color(Color.RED)));
return;
}
if (sender instanceof Player) {
playerTwo = ((Player) sender).getPlayerRef();
playerTwo = getPlayerRef((Player) sender);
} else {
playerTwo = (PlayerRef) sender;
}
@@ -276,4 +280,16 @@ public final class CommandParse extends PlaceholderCommand {
return Universe.get().getPlayerByUsername(name, NameMatching.EXACT);
}
@Nullable
private static PlayerRef getPlayerRef(@NotNull final Player player) {
final Ref<EntityStore> ref = player.getReference();
if (ref == null || !ref.isValid()) {
return null;
}
final Store<EntityStore> store = ref.getStore();
return store.getComponent(ref, PlayerRef.getComponentType());
}
}

View File

@@ -49,15 +49,15 @@ public final class CommandVersion extends PlaceholderCommand {
sender.sendMessage(Message.empty()
.insert(Message.raw("PlaceholderAPI ").color(Color.CYAN).bold(true))
.insert(Message.raw("(").color(Color.GRAY))
.insert(Message.raw("(").color(Color.LIGHT_GRAY))
.insert(Message.raw(description.getVersion().toString()).color(Color.WHITE))
.insert(Message.raw(")").color(Color.GRAY))
.insert(Message.raw("\nAuthor: ").color(Color.GRAY))
.insert(Message.raw(")").color(Color.LIGHT_GRAY))
.insert(Message.raw("\nAuthor: ").color(Color.LIGHT_GRAY))
.insert(Message.raw(description.getAuthors().stream().map(AuthorInfo::getName).collect(Collectors.joining(", "))).color(Color.WHITE))
.insert(Message.raw("\nPAPI Commands: ").color(Color.GRAY))
.insert(Message.raw("\nPAPI Commands: ").color(Color.LIGHT_GRAY))
.insert(Message.raw("/papi ").color(Color.CYAN))
.insert(Message.raw("help").color(Color.WHITE))
.insert(Message.raw("\neCloud Commands: ").color(Color.GRAY))
.insert(Message.raw("\neCloud Commands: ").color(Color.LIGHT_GRAY))
.insert(Message.raw("/papi ").color(Color.CYAN))
.insert(Message.raw("ecloud").color(Color.WHITE)));

View File

@@ -220,21 +220,7 @@ public final class LocalExpansionManager /*implements Listener*/ {
public boolean register(@NotNull final PlaceholderExpansion expansion) {
final String identifier = expansion.getIdentifier().toLowerCase(Locale.ROOT);
if (expansion instanceof Configurable<?> configurable) {
final PlaceholderAPIConfig config = configManager.config();
if (config.expansions() == null) {
config.expansions(new ConcurrentHashMap<>());
}
if (!config.expansions().containsKey(expansion.getIdentifier())) {
config.expansions().put(expansion.getIdentifier(), configurable.provideDefault());
configManager.save();
} else {
final Object expansionConfig = configManager.convertExpansion((Map<String, Object>) config.expansions().get(expansion.getIdentifier()), configurable.provideConfigType());
config.expansions().put(expansion.getIdentifier(), expansionConfig);
}
}
createConfig(expansion);
if (!expansion.canRegister()) {
return false;
@@ -307,6 +293,33 @@ public final class LocalExpansionManager /*implements Listener*/ {
return true;
}
/**
* Creates and initializes the configuration for the provided {@link PlaceholderExpansion}.
* If the expansion implements the {@link Configurable} interface, this method ensures that
* the expansion's default configuration is registered and saved if it is not already present.
* If a configuration already exists, it converts and updates it using the provided configuration type.
*
* @param expansion the {@link PlaceholderExpansion} for which the configuration is being created
*/
@ApiStatus.Internal
public void createConfig(PlaceholderExpansion expansion) {
if (expansion instanceof Configurable<?> configurable) {
final PlaceholderAPIConfig config = configManager.config();
if (config.expansions() == null) {
config.expansions(new ConcurrentHashMap<>());
}
if (!config.expansions().containsKey(expansion.getIdentifier())) {
config.expansions().put(expansion.getIdentifier(), configurable.provideDefault());
configManager.save();
} else {
final Object expansionConfig = configManager.convertExpansion((Map<String, Object>) config.expansions().get(expansion.getIdentifier()), configurable.provideConfigType());
config.expansions().put(expansion.getIdentifier(), expansionConfig);
}
}
}
@ApiStatus.Internal
public boolean unregister(@NotNull final PlaceholderExpansion expansion) {
if (expansions.remove(expansion.getIdentifier().toLowerCase(Locale.ROOT)) == null) {

View File

@@ -24,6 +24,7 @@ import java.util.Locale;
import java.util.function.Function;
import at.helpch.placeholderapi.expansion.PlaceholderExpansion;
import com.hypixel.hytale.server.core.entity.entities.Player;
import com.hypixel.hytale.server.core.universe.PlayerRef;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -37,116 +38,101 @@ public final class CharsReplacer implements Replacer {
this.closure = closure;
}
/**
* Translates placeholders within the provided text using a high-performance
* character-scanning approach.
* * <p>The method identifies placeholders delimited by the defined {@link Closure}
* (e.g., %identifier_params% or {identifier_params}). If a placeholder is
* successfully identified, the provided lookup function is used to fetch the
* corresponding {@link PlaceholderExpansion}.</p>
*
* @param text The raw text containing potential placeholders to be replaced.
* @param player The {@link PlayerRef} to contextually parse the placeholders against.
* May be {@code null} if no player context is available.
* @param lookup A function that maps a lowercase identifier string to a registered
* {@link PlaceholderExpansion}.
* @return A string with all valid placeholders replaced by their respective values.
* Returns the original text if no placeholders are found.
*/
@NotNull
@Override
public String apply(@NotNull final String text, @Nullable final PlayerRef player,
@NotNull final Function<String, @Nullable PlaceholderExpansion> lookup) {
final char head = closure.head;
int startPlaceholder = text.indexOf(head);
final char[] chars = text.toCharArray();
final StringBuilder builder = new StringBuilder(text.length());
if (startPlaceholder == -1) {
return text;
}
final StringBuilder identifier = new StringBuilder();
final StringBuilder parameters = new StringBuilder();
final int length = text.length();
final StringBuilder builder = new StringBuilder(length + (length >> 3));
int cursor = 0;
for (int i = 0; i < chars.length; i++) {
final char l = chars[i];
final char tail = closure.tail;
loop: do {
// Append plain text preceding the placeholder
if (startPlaceholder > cursor) {
builder.append(text, cursor, startPlaceholder);
}
final int endPlaceholder = text.indexOf(tail, startPlaceholder + 1);
if (endPlaceholder == -1) {
builder.append(text, startPlaceholder, length);
return builder.toString();
}
int underscoreIndex = -1;
for (int i = startPlaceholder + 1; i < endPlaceholder; i++) {
final char current = text.charAt(i);
if (current == ' ') {
// Invalid placeholder (contains space).
// Treat the opening symbol as literal text and search for the next one.
builder.append(head);
cursor = startPlaceholder + 1;
startPlaceholder = text.indexOf(head, cursor);
// Safety check: If no more placeholders exist, break to finalize
if (startPlaceholder == -1) {
break loop;
}
continue loop;
}
if (current == '_' && underscoreIndex == -1) {
underscoreIndex = i;
}
}
if (underscoreIndex == -1) {
builder.append(text, startPlaceholder, endPlaceholder + 1);
cursor = endPlaceholder + 1;
startPlaceholder = text.indexOf(head, cursor);
if (l != closure.head || i + 1 >= chars.length) {
builder.append(l);
continue;
}
String identifier = text.substring(startPlaceholder + 1, underscoreIndex);
String parameters = "";
boolean identified = false;
boolean invalid = true;
boolean hadSpace = false;
if (underscoreIndex + 1 < endPlaceholder) {
parameters = text.substring(underscoreIndex + 1, endPlaceholder);
while (++i < chars.length) {
final char p = chars[i];
if (p == ' ' && !identified) {
hadSpace = true;
break;
}
if (p == closure.tail) {
invalid = false;
break;
}
if (p == '_' && !identified) {
identified = true;
continue;
}
if (identified) {
parameters.append(p);
} else {
identifier.append(p);
}
}
final PlaceholderExpansion expansion = lookup.apply(identifier.toLowerCase(Locale.ROOT));
String replacement = null;
final String identifierString = identifier.toString();
final String lowercaseIdentifierString = identifierString.toLowerCase(Locale.ROOT);
final String parametersString = parameters.toString();
if (expansion != null) {
replacement = expansion.onPlaceholderRequest(player, parameters);
identifier.setLength(0);
parameters.setLength(0);
if (invalid) {
builder.append(closure.head).append(identifierString);
if (identified) {
builder.append('_').append(parametersString);
}
if (hadSpace) {
builder.append(' ');
}
continue;
}
if (replacement != null) {
builder.append(replacement);
} else {
// Fallback: Restore original placeholder format
builder.append(head).append(identifier);
builder.append('_').append(parameters);
builder.append(tail);
final PlaceholderExpansion placeholder = lookup.apply(lowercaseIdentifierString);
if (placeholder == null) {
builder.append(closure.head).append(identifierString);
if (identified) {
builder.append('_');
}
builder.append(parametersString).append(closure.tail);
continue;
}
cursor = endPlaceholder + 1;
startPlaceholder = text.indexOf(head, cursor);
final String replacement = placeholder.onPlaceholderRequest(player, parametersString);
if (replacement == null) {
builder.append(closure.head).append(identifierString);
} while (startPlaceholder != -1);
if (identified) {
builder.append('_');
}
if (cursor < length) {
builder.append(text, cursor, length);
builder.append(parametersString).append(closure.tail);
continue;
}
builder.append(replacement);
}
return builder.toString();
}
}
}