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 {
|
||||
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("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.Closure;
|
||||
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.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
@@ -57,6 +59,12 @@ public final class PlaceholderAPI {
|
||||
|
||||
// === 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.
|
||||
* <br>The pattern of a valid placeholder is {@literal %<identifier>_<params>%}.
|
||||
|
||||
@@ -20,11 +20,14 @@
|
||||
|
||||
package me.clip.placeholderapi;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public abstract class PlaceholderHook {
|
||||
@Nullable
|
||||
public String onRequest(final OfflinePlayer player, @NotNull final String params) {
|
||||
@@ -35,8 +38,16 @@ public abstract class PlaceholderHook {
|
||||
return onPlaceholderRequest(null, params);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Nullable
|
||||
public String onPlaceholderRequest(final Player player, @NotNull final String params) {
|
||||
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