diff --git a/eco-api/src/main/java/com/willfp/eco/core/Eco.java b/eco-api/src/main/java/com/willfp/eco/core/Eco.java index 2844cb6d..26b421cd 100644 --- a/eco-api/src/main/java/com/willfp/eco/core/Eco.java +++ b/eco-api/src/main/java/com/willfp/eco/core/Eco.java @@ -563,6 +563,30 @@ public interface Eco { void sendPacket(@NotNull Player player, @NotNull Packet packet); + /** + * Translate placeholders in a string. + * + * @param text The text. + * @param context The context. + * @return The translated text. + */ + @NotNull + String translatePlaceholders(@NotNull String text, + @NotNull PlaceholderContext context); + + /** + * Get the value of a placeholder. + * + * @param plugin The plugin that owns the placeholder. + * @param args The placeholder arguments. + * @param context The context. + * @return The value, or null if invalid. + */ + @Nullable + String getPlaceholderValue(@Nullable EcoPlugin plugin, + @NotNull String args, + @NotNull PlaceholderContext context); + /** * Get the instance of eco; the bridge between the api frontend and the implementation backend. * diff --git a/eco-api/src/main/java/com/willfp/eco/core/integrations/placeholder/PlaceholderManager.java b/eco-api/src/main/java/com/willfp/eco/core/integrations/placeholder/PlaceholderManager.java index 8729c1bb..e3b41e92 100644 --- a/eco-api/src/main/java/com/willfp/eco/core/integrations/placeholder/PlaceholderManager.java +++ b/eco-api/src/main/java/com/willfp/eco/core/integrations/placeholder/PlaceholderManager.java @@ -1,7 +1,7 @@ package com.willfp.eco.core.integrations.placeholder; -import com.github.benmanes.caffeine.cache.Cache; -import com.github.benmanes.caffeine.cache.Caffeine; +import com.google.common.collect.ImmutableSet; +import com.willfp.eco.core.Eco; import com.willfp.eco.core.EcoPlugin; import com.willfp.eco.core.map.DefaultMap; import com.willfp.eco.core.placeholder.AdditionalPlayer; @@ -10,7 +10,6 @@ import com.willfp.eco.core.placeholder.Placeholder; import com.willfp.eco.core.placeholder.PlaceholderInjectable; import com.willfp.eco.core.placeholder.RegistrablePlaceholder; import com.willfp.eco.core.placeholder.context.PlaceholderContext; -import com.willfp.eco.util.StringUtils; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -18,14 +17,10 @@ import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.Set; -import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -36,20 +31,13 @@ public final class PlaceholderManager { /** * All registered placeholders. */ - private static final DefaultMap> REGISTERED_PLACEHOLDERS = DefaultMap.createNestedMap(); + private static final DefaultMap> REGISTERED_PLACEHOLDERS = new DefaultMap<>(HashSet::new); /** * All registered arguments integrations. */ private static final Set REGISTERED_INTEGRATIONS = new HashSet<>(); - /** - * Placeholder Lookup Cache. - */ - private static final Cache> PLACEHOLDER_LOOKUP_CACHE = Caffeine.newBuilder() - .expireAfterWrite(1, TimeUnit.SECONDS) - .build(); - /** * The default PlaceholderAPI pattern; brought in for compatibility. */ @@ -107,19 +95,18 @@ public final class PlaceholderManager { * @param placeholder The arguments to register. */ public static void registerPlaceholder(@NotNull final RegistrablePlaceholder placeholder) { - Map pluginPlaceholders = REGISTERED_PLACEHOLDERS - .getOrDefault(placeholder.getPlugin(), new HashMap<>()); - - pluginPlaceholders.put(placeholder.getPattern(), placeholder); - - REGISTERED_PLACEHOLDERS.put(placeholder.getPlugin(), pluginPlaceholders); + // Storing as immutable set leads to slower times to register placeholders, but much + // faster times to access registrations. + Set pluginPlaceholders = new HashSet<>(REGISTERED_PLACEHOLDERS.get(placeholder.getPlugin())); + pluginPlaceholders.add(placeholder); + REGISTERED_PLACEHOLDERS.put(placeholder.getPlugin(), ImmutableSet.copyOf(pluginPlaceholders)); } /** * Get the result of a placeholder with respect to a player. * * @param player The player to get the result from. - * @param identifier The placeholder identifier. + * @param identifier The placeholder args. * @param plugin The plugin for the arguments. * @return The value of the arguments. */ @@ -150,46 +137,7 @@ public final class PlaceholderManager { public static String getResult(@Nullable final EcoPlugin plugin, @NotNull final String args, @NotNull final PlaceholderContext context) { - Placeholder placeholder = PLACEHOLDER_LOOKUP_CACHE.get( - new PlaceholderLookup(args, plugin, context), - (it) -> findMatchingPlaceholder(plugin, args, context) - ).orElse(null); - - if (placeholder == null) { - return null; - } - - return placeholder.getValue(args, context); - } - - /** - * Find matching placeholder. - * - * @param plugin The plugin. - * @param args The args. - * @return The placeholder. - */ - @NotNull - private static Optional findMatchingPlaceholder(@Nullable final EcoPlugin plugin, - @NotNull final String args, - @NotNull final PlaceholderContext context) { - if (plugin != null) { - Map pluginPlaceholders = REGISTERED_PLACEHOLDERS.get(plugin); - - for (Map.Entry entry : pluginPlaceholders.entrySet()) { - if (entry.getKey().matcher(args).matches()) { - return Optional.of(entry.getValue()); - } - } - } - - for (InjectablePlaceholder placeholder : context.getInjectableContext().getPlaceholderInjections()) { - if (placeholder.getPattern().matcher(args).matches()) { - return Optional.of(placeholder); - } - } - - return Optional.empty(); + return Eco.get().getPlaceholderValue(plugin, args, context); } /** @@ -261,67 +209,7 @@ public final class PlaceholderManager { @NotNull public static String translatePlaceholders(@NotNull final String text, @NotNull final PlaceholderContext context) { - String processed = text; - - /* - - Why am I doing injections at the start, and again at the end? - - Additional players let you use something like victim as a player to parse in relation to, - for example doing %victim_player_health%, which would parse the health of the victim. - - However, something like libreforge will also inject %victim_max_health%, which is unrelated - to additional players, and instead holds a constant value. So, eco saw this, smartly thought - "ah, it's an additional player, let's parse it", and then tried to parse %max_health% with - relation to the victim, which resolved to zero. So, we have to parse statics and player statics - that might include a prefix first, then additional players, then player statics with the support - of additional players. - - This was a massive headache and took so many reports before I clocked what was going on. - - Oh well, at least it's fixed now. - - */ - - List injections = context.getInjectableContext().getPlaceholderInjections(); - - for (InjectablePlaceholder injection : injections) { - processed = injection.tryTranslateQuickly(processed, context); - } - - // Prevent running 2 scans if there are no additional players. - if (!context.getAdditionalPlayers().isEmpty()) { - List found = findPlaceholdersIn(text); - - for (AdditionalPlayer additionalPlayer : context.getAdditionalPlayers()) { - for (String placeholder : found) { - String prefix = "%" + additionalPlayer.getIdentifier() + "_"; - - if (placeholder.startsWith(prefix)) { - processed = processed.replace( - placeholder, - translatePlaceholders( - "%" + StringUtils.removePrefix(prefix, placeholder), - context.copyWithPlayer(additionalPlayer.getPlayer()) - ) - ); - } - } - } - } - - processed = translateEcoPlaceholdersIn(processed, context); - - for (PlaceholderIntegration integration : REGISTERED_INTEGRATIONS) { - processed = integration.translate(processed, context.getPlayer()); - } - - // DON'T REMOVE THIS, IT'S NOT DUPLICATE CODE. - for (InjectablePlaceholder injection : injections) { - processed = injection.tryTranslateQuickly(processed, context); - } - - return processed; + return Eco.get().translatePlaceholders(text, context); } /** @@ -346,52 +234,22 @@ public final class PlaceholderManager { } /** - * Translate all eco placeholders in a given text. + * Get all registered placeholder integrations. * - * @param text The text. - * @param context The context. - * @return The text. + * @return The integrations. */ - private static String translateEcoPlaceholdersIn(@NotNull final String text, - @NotNull final PlaceholderContext context) { - StringBuilder output = new StringBuilder(); - Matcher matcher = PATTERN.matcher(text); - - while (matcher.find()) { - String placeholder = matcher.group(1); - String injectableResult = getResult(null, placeholder, context); - - if (injectableResult != null) { - matcher.appendReplacement(output, Matcher.quoteReplacement(injectableResult)); - continue; - } - - String[] parts = placeholder.split("_", 2); - - if (parts.length == 2) { - EcoPlugin plugin = EcoPlugin.getPlugin(parts[0]); - - if (plugin != null) { - String result = getResult(plugin, parts[1], context); - - if (result != null) { - matcher.appendReplacement(output, Matcher.quoteReplacement(result)); - continue; - } - } - } - - matcher.appendReplacement(output, Matcher.quoteReplacement(matcher.group(0))); - } - - matcher.appendTail(output); - return output.toString(); + public static Set getRegisteredIntegrations() { + return Set.copyOf(REGISTERED_INTEGRATIONS); } - private record PlaceholderLookup(@NotNull String identifier, - @Nullable EcoPlugin plugin, - @NotNull PlaceholderContext context) { - + /** + * Get all registered placeholders for a plugin. + * + * @param plugin The plugin. + * @return The placeholders. + */ + public static Set getRegisteredPlaceholders(@NotNull final EcoPlugin plugin) { + return REGISTERED_PLACEHOLDERS.get(plugin); } private PlaceholderManager() { diff --git a/eco-api/src/main/java/com/willfp/eco/core/placeholder/InjectablePlaceholder.java b/eco-api/src/main/java/com/willfp/eco/core/placeholder/InjectablePlaceholder.java index cbeeac30..996e9b40 100644 --- a/eco-api/src/main/java/com/willfp/eco/core/placeholder/InjectablePlaceholder.java +++ b/eco-api/src/main/java/com/willfp/eco/core/placeholder/InjectablePlaceholder.java @@ -1,15 +1,20 @@ package com.willfp.eco.core.placeholder; -import com.willfp.eco.core.Eco; import com.willfp.eco.core.EcoPlugin; -import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; /** * Placeholders that can be injected into {@link PlaceholderInjectable} objects. */ public interface InjectablePlaceholder extends Placeholder { + /** + * Get the plugin that holds the arguments. + * + * @return The plugin. + */ + @Nullable @Override - default @NotNull EcoPlugin getPlugin() { - return Eco.get().getEcoPlugin(); + default EcoPlugin getPlugin() { + return null; } } diff --git a/eco-api/src/main/java/com/willfp/eco/core/placeholder/Placeholder.java b/eco-api/src/main/java/com/willfp/eco/core/placeholder/Placeholder.java index 26a21b0f..cf9ff0cf 100644 --- a/eco-api/src/main/java/com/willfp/eco/core/placeholder/Placeholder.java +++ b/eco-api/src/main/java/com/willfp/eco/core/placeholder/Placeholder.java @@ -16,7 +16,7 @@ public interface Placeholder { * * @return The plugin. */ - @NotNull + @Nullable EcoPlugin getPlugin(); /** diff --git a/eco-api/src/main/java/com/willfp/eco/core/placeholder/PlayerPlaceholder.java b/eco-api/src/main/java/com/willfp/eco/core/placeholder/PlayerPlaceholder.java index bfa45068..cc4fbb67 100644 --- a/eco-api/src/main/java/com/willfp/eco/core/placeholder/PlayerPlaceholder.java +++ b/eco-api/src/main/java/com/willfp/eco/core/placeholder/PlayerPlaceholder.java @@ -46,7 +46,7 @@ public final class PlayerPlaceholder implements RegistrablePlaceholder { @NotNull final Function<@NotNull Player, @Nullable String> function) { this.plugin = plugin; this.identifier = identifier; - this.pattern = Pattern.compile(identifier); + this.pattern = Pattern.compile(identifier, Pattern.LITERAL); this.function = function; } diff --git a/eco-api/src/main/java/com/willfp/eco/core/placeholder/PlayerStaticPlaceholder.java b/eco-api/src/main/java/com/willfp/eco/core/placeholder/PlayerStaticPlaceholder.java index df330a08..75a490cc 100644 --- a/eco-api/src/main/java/com/willfp/eco/core/placeholder/PlayerStaticPlaceholder.java +++ b/eco-api/src/main/java/com/willfp/eco/core/placeholder/PlayerStaticPlaceholder.java @@ -38,7 +38,7 @@ public final class PlayerStaticPlaceholder implements InjectablePlaceholder { public PlayerStaticPlaceholder(@NotNull final String identifier, @NotNull final Function<@NotNull Player, @Nullable String> function) { this.identifier = "%" + identifier + "%"; - this.pattern = Pattern.compile(identifier); + this.pattern = Pattern.compile(identifier, Pattern.LITERAL); this.function = function; } diff --git a/eco-api/src/main/java/com/willfp/eco/core/placeholder/PlayerlessPlaceholder.java b/eco-api/src/main/java/com/willfp/eco/core/placeholder/PlayerlessPlaceholder.java index 131b2bdc..9d69d2e4 100644 --- a/eco-api/src/main/java/com/willfp/eco/core/placeholder/PlayerlessPlaceholder.java +++ b/eco-api/src/main/java/com/willfp/eco/core/placeholder/PlayerlessPlaceholder.java @@ -39,7 +39,7 @@ public final class PlayerlessPlaceholder implements RegistrablePlaceholder { @NotNull final String identifier, @NotNull final Supplier<@Nullable String> function) { this.plugin = plugin; - this.pattern = Pattern.compile(identifier); + this.pattern = Pattern.compile(identifier, Pattern.LITERAL); this.function = function; } diff --git a/eco-api/src/main/java/com/willfp/eco/core/placeholder/RegistrablePlaceholder.java b/eco-api/src/main/java/com/willfp/eco/core/placeholder/RegistrablePlaceholder.java index 6db0b631..b12bab23 100644 --- a/eco-api/src/main/java/com/willfp/eco/core/placeholder/RegistrablePlaceholder.java +++ b/eco-api/src/main/java/com/willfp/eco/core/placeholder/RegistrablePlaceholder.java @@ -1,5 +1,6 @@ package com.willfp.eco.core.placeholder; +import com.willfp.eco.core.EcoPlugin; import com.willfp.eco.core.integrations.placeholder.PlaceholderManager; import org.jetbrains.annotations.NotNull; @@ -7,6 +8,15 @@ import org.jetbrains.annotations.NotNull; * Represents a placeholder that can be registered. */ public interface RegistrablePlaceholder extends Placeholder { + /** + * Get the plugin that holds the arguments. + * + * @return The plugin. + */ + @NotNull + @Override + EcoPlugin getPlugin(); + /** * Register the arguments. * diff --git a/eco-api/src/main/java/com/willfp/eco/core/placeholder/StaticPlaceholder.java b/eco-api/src/main/java/com/willfp/eco/core/placeholder/StaticPlaceholder.java index 5831245a..fb24d13c 100644 --- a/eco-api/src/main/java/com/willfp/eco/core/placeholder/StaticPlaceholder.java +++ b/eco-api/src/main/java/com/willfp/eco/core/placeholder/StaticPlaceholder.java @@ -37,7 +37,7 @@ public final class StaticPlaceholder implements InjectablePlaceholder { public StaticPlaceholder(@NotNull final String identifier, @NotNull final Supplier<@Nullable String> function) { this.identifier = "%" + identifier + "%"; - this.pattern = Pattern.compile(identifier); + this.pattern = Pattern.compile(identifier, Pattern.LITERAL); this.function = function; } diff --git a/eco-api/src/main/java/com/willfp/eco/core/placeholder/templates/SimpleInjectablePlaceholder.java b/eco-api/src/main/java/com/willfp/eco/core/placeholder/templates/SimpleInjectablePlaceholder.java index aeae2984..2cfba6d0 100644 --- a/eco-api/src/main/java/com/willfp/eco/core/placeholder/templates/SimpleInjectablePlaceholder.java +++ b/eco-api/src/main/java/com/willfp/eco/core/placeholder/templates/SimpleInjectablePlaceholder.java @@ -28,7 +28,7 @@ public abstract class SimpleInjectablePlaceholder implements InjectablePlacehold */ protected SimpleInjectablePlaceholder(@NotNull final String identifier) { this.identifier = identifier; - this.pattern = Pattern.compile(identifier); + this.pattern = Pattern.compile(identifier, Pattern.LITERAL); } @Override diff --git a/eco-api/src/main/java/com/willfp/eco/core/placeholder/templates/SimplePlaceholder.java b/eco-api/src/main/java/com/willfp/eco/core/placeholder/templates/SimplePlaceholder.java index 460d4fb2..36e37d71 100644 --- a/eco-api/src/main/java/com/willfp/eco/core/placeholder/templates/SimplePlaceholder.java +++ b/eco-api/src/main/java/com/willfp/eco/core/placeholder/templates/SimplePlaceholder.java @@ -37,7 +37,7 @@ public abstract class SimplePlaceholder implements RegistrablePlaceholder { @NotNull final String identifier) { this.plugin = plugin; this.identifier = identifier; - this.pattern = Pattern.compile(identifier); + this.pattern = Pattern.compile(identifier, Pattern.LITERAL); } @Override diff --git a/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/placeholder/PlaceholderLookup.kt b/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/placeholder/PlaceholderLookup.kt new file mode 100644 index 00000000..ce0d944f --- /dev/null +++ b/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/placeholder/PlaceholderLookup.kt @@ -0,0 +1,46 @@ +package com.willfp.eco.internal.placeholder + +import com.willfp.eco.core.EcoPlugin +import com.willfp.eco.core.integrations.placeholder.PlaceholderManager +import com.willfp.eco.core.placeholder.InjectablePlaceholder +import com.willfp.eco.core.placeholder.Placeholder +import java.util.regex.Pattern + +class PlaceholderLookup( + val args: String, + val plugin: EcoPlugin?, + private val injections: Collection? +) { + fun findMatchingPlaceholder(): Placeholder? { + if (plugin != null) { + val pluginPlaceholders = PlaceholderManager.getRegisteredPlaceholders(plugin) + for (placeholder in pluginPlaceholders) { + if (placeholder.matches(this)) { + return placeholder + } + } + } + + val injections = injections + + if (injections != null) { + for (placeholder in injections) { + if (placeholder.matches(this)) { + return placeholder + } + } + } + + return null + } + + private fun Placeholder.matches(lookup: PlaceholderLookup): Boolean { + val pattern = this.pattern + val patternString = pattern.pattern() + + val patternFlags = pattern.flags() + val isLiteral = Pattern.LITERAL and patternFlags != 0 + + return if (isLiteral) lookup.args == patternString else pattern.matcher(lookup.args).matches() + } +} diff --git a/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/placeholder/PlaceholderParser.kt b/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/placeholder/PlaceholderParser.kt new file mode 100644 index 00000000..b4aa379d --- /dev/null +++ b/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/placeholder/PlaceholderParser.kt @@ -0,0 +1,206 @@ +package com.willfp.eco.internal.placeholder + +import com.github.benmanes.caffeine.cache.Caffeine +import com.willfp.eco.core.EcoPlugin +import com.willfp.eco.core.integrations.placeholder.PlaceholderManager +import com.willfp.eco.core.placeholder.InjectablePlaceholder +import com.willfp.eco.core.placeholder.Placeholder +import com.willfp.eco.core.placeholder.context.PlaceholderContext +import com.willfp.eco.util.StringUtils +import java.util.Optional +import java.util.concurrent.TimeUnit + +/* + +A lot of methods here are centered around minimising calls to getPlaceholderInjections, +which tends to be slow for things like configs. + + */ + +class PlaceholderParser { + private val placeholderRegex = Regex("%([^% ]+)%") + + private val placeholderLookupCache = Caffeine.newBuilder() + .expireAfterWrite(1, TimeUnit.SECONDS) + .build>() + + fun translatePlacholders(text: String, context: PlaceholderContext): String { + return translatePlacholders(text, context, context.injectableContext.placeholderInjections) + } + + private fun translatePlacholders( + text: String, + context: PlaceholderContext, + injections: Collection, + translateEcoPlaceholders: Boolean = true + ): String { + /* + + Why am I doing injections at the start, and again at the end? + + Additional players let you use something like victim as a player to parse in relation to, + for example doing %victim_player_health%, which would parse the health of the victim. + + However, something like libreforge will also inject %victim_max_health%, which is unrelated + to additional players, and instead holds a constant value. So, eco saw this, smartly thought + "ah, it's an additional player, let's parse it", and then tried to parse %max_health% with + relation to the victim, which resolved to zero. So, we have to parse statics and player statics + that might include a prefix first, then additional players, then player statics with the support + of additional players. + + This was a massive headache and took so many reports before I clocked what was going on. + + Oh well, at least it's fixed now. + + */ + + // Apply injections first + var processed = injections.fold(text) { acc, injection -> + injection.tryTranslateQuickly(acc, context) + } + + // Prevent running 2 scans if there are no additional players. + if (context.additionalPlayers.isNotEmpty()) { + val found = PlaceholderManager.findPlaceholdersIn(text) + for (additionalPlayer in context.additionalPlayers) { + val prefix = "%${additionalPlayer.identifier}_" + processed = found.fold(processed) { acc, placeholder -> + if (placeholder.startsWith(prefix)) { + val newPlaceholder = "%${StringUtils.removePrefix(prefix, placeholder)}" + val translation = PlaceholderManager.translatePlaceholders( + newPlaceholder, + context.copyWithPlayer(additionalPlayer.player) + ) + acc.replace(placeholder, translation) + } else { + acc + } + } + } + } + + // Translate eco placeholders + if (translateEcoPlaceholders) { + processed = translateEcoPlaceholdersIn(processed, context, injections) + } + + // Apply registered integrations + processed = PlaceholderManager.getRegisteredIntegrations().fold(processed) { acc, integration -> + integration.translate(acc, context.player) + } + + // Apply injections again + return injections.fold(processed) { acc, injection -> + injection.tryTranslateQuickly(acc, context) + } + } + + fun getPlaceholderResult( + plugin: EcoPlugin?, + args: String, + context: PlaceholderContext + ): String? { + // Only scan for injections if plugin is null. + val injections = if (plugin == null) context.injectableContext.placeholderInjections else null + + return doGetResult(plugin, args, injections, context) + } + + // Injections are sent separately here to prevent multiple calls to getPlaceholderInjections + private fun doGetResult( + plugin: EcoPlugin?, + args: String?, + injections: Collection?, + context: PlaceholderContext + ): String? { + if (args == null) { + return null + } + + val lookup = PlaceholderLookup(args, plugin, injections) + + val placeholder = placeholderLookupCache.get(lookup) { + Optional.ofNullable(it.findMatchingPlaceholder()) + }.orElse(null) ?: return null + + return placeholder.getValue(args, context) + } + + private fun translateEcoPlaceholdersIn( + text: String, + context: PlaceholderContext, + injections: Collection + ): String { + val output = StringBuilder() + var lastAppendPosition = 0 + + for (matchResult in placeholderRegex.findAll(text)) { + val placeholder = matchResult.groups[1]?.value ?: "" + + val injectableResult = doGetResult(null, placeholder, injections, context) + + val parts = placeholder.split("_", limit = 2) + + var result: String? = null + + if (injectableResult != null) { + result = injectableResult + } else if (parts.size == 2) { + val plugin = EcoPlugin.getPlugin(parts[0]) + + if (plugin != null) { + result = doGetResult(plugin, parts[1], null, context) + } + } + + output.append(text.substring(lastAppendPosition, matchResult.range.first)) + + output.append(result ?: matchResult.value) + + lastAppendPosition = matchResult.range.last + 1 + } + + output.append(text.substring(lastAppendPosition)) + return output.toString() + } + + fun parseIndividualPlaceholders(strings: Collection, context: PlaceholderContext): Collection { + val injections = context.injectableContext.placeholderInjections + + return strings.map { + parseIndividualEcoPlaceholder(it, context, injections) + ?: translatePlacholders( + it, + context, + injections, + translateEcoPlaceholders = false + ) // Default to slower translation + } + } + + private fun parseIndividualEcoPlaceholder( + string: String, + context: PlaceholderContext, + injections: Collection + ): String? { + val placeholder = string.substring(1, string.length - 1) + + val injectableResult = doGetResult(null, placeholder, injections, context) + + if (injectableResult != null) { + return injectableResult + } + + val parts = placeholder.split("_", limit = 2) + + if (parts.size == 2) { + val plugin = EcoPlugin.getPlugin(parts[0]) + + if (plugin != null) { + return doGetResult(plugin, parts[1], null, context) + } + } + + return null + } +} diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/eco/internal/spigot/EcoImpl.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/eco/internal/spigot/EcoImpl.kt index 3ee96179..17220a1c 100644 --- a/eco-core/core-plugin/src/main/kotlin/com/willfp/eco/internal/spigot/EcoImpl.kt +++ b/eco-core/core-plugin/src/main/kotlin/com/willfp/eco/internal/spigot/EcoImpl.kt @@ -37,6 +37,7 @@ import com.willfp.eco.internal.gui.menu.renderedInventory import com.willfp.eco.internal.gui.slot.EcoSlotBuilder import com.willfp.eco.internal.integrations.PAPIExpansion import com.willfp.eco.internal.logging.EcoLogger +import com.willfp.eco.internal.placeholder.PlaceholderParser import com.willfp.eco.internal.proxy.EcoProxyFactory import com.willfp.eco.internal.scheduling.EcoScheduler import com.willfp.eco.internal.spigot.data.DataYml @@ -90,10 +91,13 @@ class EcoImpl : EcoSpigotPlugin(), Eco { if (this.configYml.getBool("use-safer-namespacedkey-creation")) SafeInternalNamespacedKeyFactory() else FastInternalNamespacedKeyFactory() + private val placeholderParser = PlaceholderParser() + private val crunchHandler = DelegatedExpressionHandler( this, if (this.configYml.getBool("use-immediate-placeholder-translation-for-math")) - ImmediatePlaceholderTranslationExpressionHandler() else LazyPlaceholderTranslationExpressionHandler() + ImmediatePlaceholderTranslationExpressionHandler(placeholderParser) + else LazyPlaceholderTranslationExpressionHandler(placeholderParser), ) override fun createScheduler(plugin: EcoPlugin) = @@ -334,4 +338,10 @@ class EcoImpl : EcoSpigotPlugin(), Eco { override fun sendPacket(player: Player, packet: Packet) = this.getProxy(PacketHandlerProxy::class.java).sendPacket(player, packet) + + override fun translatePlaceholders(text: String, context: PlaceholderContext) = + placeholderParser.translatePlacholders(text, context) + + override fun getPlaceholderValue(plugin: EcoPlugin?, args: String, context: PlaceholderContext) = + placeholderParser.getPlaceholderResult(plugin, args, context) } diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/eco/internal/spigot/math/ExpressionHandlers.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/eco/internal/spigot/math/ExpressionHandlers.kt index 801c6276..9923f35a 100644 --- a/eco-core/core-plugin/src/main/kotlin/com/willfp/eco/internal/spigot/math/ExpressionHandlers.kt +++ b/eco-core/core-plugin/src/main/kotlin/com/willfp/eco/internal/spigot/math/ExpressionHandlers.kt @@ -4,6 +4,7 @@ import com.github.benmanes.caffeine.cache.Cache import com.github.benmanes.caffeine.cache.Caffeine import com.willfp.eco.core.integrations.placeholder.PlaceholderManager import com.willfp.eco.core.placeholder.context.PlaceholderContext +import com.willfp.eco.internal.placeholder.PlaceholderParser import redempt.crunch.CompiledExpression import redempt.crunch.Crunch import redempt.crunch.data.FastNumberParsing @@ -27,7 +28,9 @@ interface ExpressionHandler { fun evaluate(expression: String, context: PlaceholderContext): Double } -class ImmediatePlaceholderTranslationExpressionHandler : ExpressionHandler { +class ImmediatePlaceholderTranslationExpressionHandler( + private val placeholderParser: PlaceholderParser +) : ExpressionHandler { private val cache: Cache = Caffeine.newBuilder() .expireAfterAccess(500, TimeUnit.MILLISECONDS) .build() @@ -37,7 +40,7 @@ class ImmediatePlaceholderTranslationExpressionHandler : ExpressionHandler { } override fun evaluate(expression: String, context: PlaceholderContext): Double { - val translatedExpression = PlaceholderManager.translatePlaceholders(expression, context) + val translatedExpression = placeholderParser.translatePlacholders(expression, context) val compiled = cache.get(translatedExpression) { runCatching { Crunch.compileExpression(translatedExpression, env) } @@ -48,15 +51,16 @@ class ImmediatePlaceholderTranslationExpressionHandler : ExpressionHandler { } } -class LazyPlaceholderTranslationExpressionHandler : ExpressionHandler { +class LazyPlaceholderTranslationExpressionHandler( + private val placeholderParser: PlaceholderParser +) : ExpressionHandler { private val cache: Cache = Caffeine.newBuilder() .build() override fun evaluate(expression: String, context: PlaceholderContext): Double { val placeholders = PlaceholderManager.findPlaceholdersIn(expression) - val placeholderValues = placeholders - .map { PlaceholderManager.translatePlaceholders(it, context) } + val placeholderValues = placeholderParser.parseIndividualPlaceholders(placeholders, context) .map { runCatching { FastNumberParsing.parseDouble(it) }.getOrDefault(0.0) } .toDoubleArray()