mirror of
https://github.com/PlaceholderAPI/PlaceholderAPI
synced 2025-10-27 17:21:58 +01:00
Uhh
This commit is contained in:
@@ -25,7 +25,7 @@ repositories {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("org.bstats:bstats-bukkit:3.0.1")
|
implementation("org.bstats:bstats-bukkit:3.0.1")
|
||||||
implementation("net.kyori:adventure-platform-bukkit:4.3.3")
|
implementation("net.kyori:adventure-platform-bukkit:4.4.1")
|
||||||
|
|
||||||
//compileOnly("org.spigotmc:spigot-api:1.21-R0.1-SNAPSHOT")
|
//compileOnly("org.spigotmc:spigot-api:1.21-R0.1-SNAPSHOT")
|
||||||
compileOnly("dev.folia:folia-api:1.20.1-R0.1-SNAPSHOT")
|
compileOnly("dev.folia:folia-api:1.20.1-R0.1-SNAPSHOT")
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ import me.clip.placeholderapi.replacer.CharsReplacer;
|
|||||||
import me.clip.placeholderapi.replacer.Replacer;
|
import me.clip.placeholderapi.replacer.Replacer;
|
||||||
import me.clip.placeholderapi.replacer.Replacer.Closure;
|
import me.clip.placeholderapi.replacer.Replacer.Closure;
|
||||||
import me.clip.placeholderapi.util.Msg;
|
import me.clip.placeholderapi.util.Msg;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.TextReplacementConfig;
|
||||||
import org.bukkit.OfflinePlayer;
|
import org.bukkit.OfflinePlayer;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.plugin.Plugin;
|
import org.bukkit.plugin.Plugin;
|
||||||
@@ -57,6 +59,12 @@ public final class PlaceholderAPI {
|
|||||||
|
|
||||||
// === Current API ===
|
// === Current API ===
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static Component setComponentPlaceholders(final OfflinePlayer player, @NotNull final Component component) {
|
||||||
|
// change charsreplacer to custom that returns component instead of string - this is going to suck mega
|
||||||
|
return component.replaceText(config -> config.match(PLACEHOLDER_PATTERN).replacement((result, builder) -> builder.content(REPLACER_PERCENT.apply(builder.content(), player, PlaceholderAPIPlugin.getInstance().getLocalExpansionManager()::getExpansion))));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Translates all placeholders into their corresponding values.
|
* Translates all placeholders into their corresponding values.
|
||||||
* <br>The pattern of a valid placeholder is {@literal %<identifier>_<params>%}.
|
* <br>The pattern of a valid placeholder is {@literal %<identifier>_<params>%}.
|
||||||
|
|||||||
@@ -20,11 +20,14 @@
|
|||||||
|
|
||||||
package me.clip.placeholderapi;
|
package me.clip.placeholderapi;
|
||||||
|
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
import org.bukkit.OfflinePlayer;
|
import org.bukkit.OfflinePlayer;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
public abstract class PlaceholderHook {
|
public abstract class PlaceholderHook {
|
||||||
@Nullable
|
@Nullable
|
||||||
public String onRequest(final OfflinePlayer player, @NotNull final String params) {
|
public String onRequest(final OfflinePlayer player, @NotNull final String params) {
|
||||||
@@ -35,8 +38,16 @@ public abstract class PlaceholderHook {
|
|||||||
return onPlaceholderRequest(null, params);
|
return onPlaceholderRequest(null, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
@Nullable
|
@Nullable
|
||||||
public String onPlaceholderRequest(final Player player, @NotNull final String params) {
|
public String onPlaceholderRequest(final Player player, @NotNull final String params) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Component onPlaceholderComponentRequest(final Player player, @NotNull final String params) {
|
||||||
|
final String result = onPlaceholderRequest(player, params);
|
||||||
|
|
||||||
|
return result == null ? null : Component.text(result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,224 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of adventure, licensed under the MIT License.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2017-2025 KyoriPowered
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
package me.clip.placeholderapi.replacer;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.regex.MatchResult;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
|
||||||
|
import net.kyori.adventure.text.*;
|
||||||
|
import net.kyori.adventure.text.event.HoverEvent;
|
||||||
|
import net.kyori.adventure.text.format.Style;
|
||||||
|
import net.kyori.adventure.text.renderer.ComponentRenderer;
|
||||||
|
import org.bukkit.OfflinePlayer;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A renderer performing a replacement on every {@link TextComponent} element of a component tree.
|
||||||
|
*/
|
||||||
|
final class ComponentCharsReplacer implements ComponentRenderer<ComponentCharsReplacer.State> {
|
||||||
|
//static final TextReplacementRenderer INSTANCE = new TextReplacementRenderer();
|
||||||
|
private final OfflinePlayer player;
|
||||||
|
private final Function<String, @Nullable PlaceholderExpansion> lookup;
|
||||||
|
|
||||||
|
public ComponentCharsReplacer(@Nullable final OfflinePlayer player,
|
||||||
|
@NotNull final Function<String, @Nullable PlaceholderExpansion> lookup) {
|
||||||
|
this.player = player;
|
||||||
|
this.lookup = lookup;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Component render(final Component component, final State state) {
|
||||||
|
if (!state.running) return component;
|
||||||
|
final boolean prevFirstMatch = state.firstMatch;
|
||||||
|
state.firstMatch = true;
|
||||||
|
|
||||||
|
final List<Component> oldChildren = component.children();
|
||||||
|
final int oldChildrenSize = oldChildren.size();
|
||||||
|
Style oldStyle = component.style();
|
||||||
|
List<Component> children = null;
|
||||||
|
Component modified = component;
|
||||||
|
// replace the component itself
|
||||||
|
if (component instanceof TextComponent) {
|
||||||
|
TextComponent tc = (TextComponent) component;
|
||||||
|
final String content = tc.content();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
final Matcher matcher = state.pattern.matcher(content);
|
||||||
|
int replacedUntil = 0; // last index handled
|
||||||
|
while (matcher.find()) {
|
||||||
|
final PatternReplacementResult result = state.continuer.shouldReplace(matcher, ++state.matchCount, state.replaceCount);
|
||||||
|
|
||||||
|
if (matcher.start() == 0) {
|
||||||
|
// if we're a full match, modify the component directly
|
||||||
|
if (matcher.end() == content.length()) {
|
||||||
|
final ComponentLike replacement = state.replacement.apply(matcher, Component.text().content(matcher.group())
|
||||||
|
.style(component.style()));
|
||||||
|
|
||||||
|
modified = replacement == null ? Component.empty() : replacement.asComponent();
|
||||||
|
|
||||||
|
if (modified.style().hoverEvent() != null) {
|
||||||
|
oldStyle = oldStyle.hoverEvent(null); // Remove original hover if it has been replaced completely
|
||||||
|
}
|
||||||
|
|
||||||
|
// merge style of the match into this component to prevent unexpected loss of style
|
||||||
|
modified = modified.style(modified.style().merge(component.style(), Style.Merge.Strategy.IF_ABSENT_ON_TARGET));
|
||||||
|
|
||||||
|
if (children == null) { // Prepare children
|
||||||
|
children = new ArrayList<>(oldChildrenSize + modified.children().size());
|
||||||
|
children.addAll(modified.children());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// otherwise, work on a child of the root node
|
||||||
|
modified = Component.text("", component.style());
|
||||||
|
final ComponentLike child = state.replacement.apply(matcher, Component.text().content(matcher.group()));
|
||||||
|
if (child != null) {
|
||||||
|
if (children == null) {
|
||||||
|
children = new ArrayList<>(oldChildrenSize + 1);
|
||||||
|
}
|
||||||
|
children.add(child.asComponent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (children == null) {
|
||||||
|
children = new ArrayList<>(oldChildrenSize + 2);
|
||||||
|
}
|
||||||
|
if (state.firstMatch) {
|
||||||
|
// truncate parent to content before match
|
||||||
|
modified = ((TextComponent) component).content(content.substring(0, matcher.start()));
|
||||||
|
} else if (replacedUntil < matcher.start()) {
|
||||||
|
children.add(Component.text(content.substring(replacedUntil, matcher.start())));
|
||||||
|
}
|
||||||
|
final ComponentLike builder = state.replacement.apply(matcher, Component.text().content(matcher.group()));
|
||||||
|
if (builder != null) {
|
||||||
|
children.add(builder.asComponent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state.replaceCount++;
|
||||||
|
state.firstMatch = false;
|
||||||
|
replacedUntil = matcher.end();
|
||||||
|
}
|
||||||
|
if (replacedUntil < content.length()) {
|
||||||
|
// append trailing content
|
||||||
|
if (replacedUntil > 0) {
|
||||||
|
if (children == null) {
|
||||||
|
children = new ArrayList<>(oldChildrenSize);
|
||||||
|
}
|
||||||
|
children.add(Component.text(content.substring(replacedUntil)));
|
||||||
|
}
|
||||||
|
// otherwise, we haven't modified the component, so nothing to change
|
||||||
|
}
|
||||||
|
} else if (modified instanceof TranslatableComponent) { // get TranslatableComponent with() args
|
||||||
|
final List<TranslationArgument> args = ((TranslatableComponent) modified).arguments();
|
||||||
|
List<TranslationArgument> newArgs = null;
|
||||||
|
for (int i = 0, size = args.size(); i < size; i++) {
|
||||||
|
final TranslationArgument original = args.get(i);
|
||||||
|
final TranslationArgument replaced = original.value() instanceof Component ? TranslationArgument.component(this.render((Component) original.value(), state)) : original;
|
||||||
|
if (replaced != original) {
|
||||||
|
if (newArgs == null) {
|
||||||
|
newArgs = new ArrayList<>(size);
|
||||||
|
if (i > 0) {
|
||||||
|
newArgs.addAll(args.subList(0, i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (newArgs != null) {
|
||||||
|
newArgs.add(replaced);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (newArgs != null) {
|
||||||
|
modified = ((TranslatableComponent) modified).arguments(newArgs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Only visit children if we're running
|
||||||
|
if (state.running) {
|
||||||
|
// hover event
|
||||||
|
if (state.replaceInsideHoverEvents) {
|
||||||
|
final HoverEvent<?> event = oldStyle.hoverEvent();
|
||||||
|
if (event != null) {
|
||||||
|
final HoverEvent<?> rendered = event.withRenderedValue(this, state);
|
||||||
|
if (event != rendered) {
|
||||||
|
modified = modified.style(s -> s.hoverEvent(rendered));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Children
|
||||||
|
boolean first = true;
|
||||||
|
for (int i = 0; i < oldChildrenSize; i++) {
|
||||||
|
final Component child = oldChildren.get(i);
|
||||||
|
final Component replaced = this.render(child, state);
|
||||||
|
if (replaced != child) {
|
||||||
|
if (children == null) {
|
||||||
|
children = new ArrayList<>(oldChildrenSize);
|
||||||
|
}
|
||||||
|
if (first) {
|
||||||
|
children.addAll(oldChildren.subList(0, i));
|
||||||
|
}
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
if (children != null) {
|
||||||
|
children.add(replaced);
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// we're not visiting children, re-add original children if necessary
|
||||||
|
if (children != null) {
|
||||||
|
children.addAll(oldChildren);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state.firstMatch = prevFirstMatch;
|
||||||
|
// Update the modified component with new children
|
||||||
|
if (children != null) {
|
||||||
|
return modified.children(children);
|
||||||
|
}
|
||||||
|
return modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class State {
|
||||||
|
final Pattern pattern;
|
||||||
|
final BiFunction<MatchResult, TextComponent.Builder, @Nullable ComponentLike> replacement;
|
||||||
|
final TextReplacementConfig.Condition continuer;
|
||||||
|
final boolean replaceInsideHoverEvents;
|
||||||
|
boolean running = true;
|
||||||
|
int matchCount = 0;
|
||||||
|
int replaceCount = 0;
|
||||||
|
boolean firstMatch = true;
|
||||||
|
|
||||||
|
State(final Pattern pattern, final BiFunction<MatchResult, TextComponent.Builder, @Nullable ComponentLike> replacement, final TextReplacementConfig.Condition continuer, final boolean replaceInsideHoverEvents) {
|
||||||
|
this.pattern = pattern;
|
||||||
|
this.replacement = replacement;
|
||||||
|
this.continuer = continuer;
|
||||||
|
this.replaceInsideHoverEvents = replaceInsideHoverEvents;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user